[HTTP 상태코드] 4xx - 클라이언트 오류, 5xx - 서버 오류

myeonji·2022년 8월 11일
0

HTTP 웹 기본 지식

목록 보기
22/22

4xx (Client Error)

클라이언트 오류

  • 클라이언트의 요청에 잘못된 문법 등으로 서버가 요청을 수행할 수 없음
  • 오류의 원인이 클라이언트에 있음
  • ⭐ 중요! 클라이언트가 이미 잘못된 요청, 데이터를 보내고 있기 때문에 똑같은 재시도가 실패함 (5xx 오류는 서버가 고쳐지면 클라이언트 요청은 추후 성공 가능, ex)데이터베이스가 복구된다면.. )

400 Bad Request

클라이언트가 잘못된 요청을 해서 서버가 요청을 처리할 수 없음

  • 요청 구문, 메시지 등등 오류
  • 클라이언트는 요청 내용을 다시 검토하고 보내야함
  • 예) 요청 파라미터가 잘못되거나, API 스펙이 맞지 않을 때

401 Unauthorized

클라이언트가 해당 리소스에 대한 인증이 필요함

  • 인증(Authentication) 되지 않음
  • 401 오류 발생시 응답에 WWW-Authenticate 헤더와 함께 인증 방법을 설명
  • 참고
    - 인증(Authentication): 본인이 누구인지 확인, (로그인)
    • 인가(Authorization): 권한부여 (ADMIN 권한처럼 특정 리소스에 접근할 수 있는 권한, 인증이 있어야 인가가 있음)
    • 오류 메시지가 Unauthorized 이지만 인증 되지 않음 (오류 이름이 아쉬움..)

403 Forbidden

서버가 요청을 이해했지만 승인을 거부함

  • 주로 인증 자격 증명은 있지만, 접근 권한이 불충분한 경우
  • 예) 어드민 등급이 아닌 사용자가 로그인은 했지만, 어드민 등급의 리소스에 접근하는 경우

404 Not Found

요청 리소스를 찾을 수 없음

  • 요청 리소스가 서버에 없음
  • 또는 클라이언트가 권한이 부족한 리소스에 접근할 때 해당 리소스를 숨기고 싶을 때

5xx (Server Error)

서버 오류

  • 서버 문제로 오류 발생
  • 서버에 문제가 있기 때문에 재시도 하면 성공할 수도 있음 (서버가 복구 되거나 등등)

500 Internal Server Error

서버 문제로 오류 발생, 애매하면 500 오류

  • 서버 내부 문제로 오류 발생
  • 애매하면 500 오류

503 Service Unavailable

서비스 이용 불가

  • 서버가 일시적인 과부하 또는 예정된 작업으로 잠시 요청을 처리할 수 없음
  • Retry-After 헤더 필드로 얼마뒤에 복구되는지 보낼 수도 있음

  • 웬만하면 서버에서는 5xx 오류를 발생시키면 안된다.
    ex) 사용자의 잔고가 부족한 경우, 스무살 이상 이용가능한 서비스에서 스무살 이하 사용자가 요청한 경우 등은 5xx 오류를 내면 안된다.
    -> 오류는 서버에 문제가 있을 때 발생시켜야 한다. (쿼리문제, 널포인터익셉션 등..)
    -> 위의 예시는 어떻게 보면 정상 요청인 것이다. (예외 케이스일 뿐, 서버 문제가 아님)

  • 컨트롤러와 서비스 간의 예외처리에 대해서는 ControllerAdvice 찾아보기!

  • 위의 예시 중에 스무살 이하 사용자가 요청한 경우 5xx 에러가 아닌 다른 에러를 출력해야 하는데, 400? 200?
    -> 마침 내가 궁금했던 점을 어떤 분께서 질문을 올리셨는데, 인프런 김영한님의 답변이 이해가 잘 되어서 가져왔다.

여기서 먼저 5xx를 제외한 선택지가 400이나 200으로 처리해야 하는데요.

클라이언트와 서버가 서로 API 스펙을 맞출 때 만약 나이는 20살 이하로 넘기면 안됩니다. 라고 HTTP API 스펙에 정의를 했으면 400으로 응답을 반환하는 것이 맞습니다. 이것은 명확하게 클라이언트가 서로간의 약속을 지키지 않은 것이지요. 클라이언트는 검증 로직을 만들어서 20살 이하면 서버로 요청이 넘어가지 못하게 막아야 합니다.

그런데 실무에서 개발을 해보면 항상 이렇게 단순하게 문제가 끝나지는 않습니다.

클라이언트가 서버가 요구하는 스펙을 다 지켰을 때 발생하는 비즈니스 예외들을 어떻게 처리할까 하는 것이지요.

지금부터 말씀드리는 내용은 정답이라기 보다는 제가 개인적으로 선호하는 방식을 설명드리겠습니다.

예를 들어서 회원 가입을 신청할 때 연령과 관계없이 가입 신청을 받아야 하는데, 일반적인 경우는 바로 회원 가입이 되지만, 20살 이하인 경우는 내부 심사를 위해서 보류 상태로 반환해야 할 때, 어떻게 응답해야 하는가?

비슷한 시나리오로 보험금을 지급해 달라고 서버에 요청했는데 HTTP API 스펙의 요청은 모두 만족했지만, 비즈니스 로직상 이 사람이 요건을 모두 충족하지 못해서 지급이 승인 거절되는 경우에는 어떻게 응답해야 하는가?

이런 경우처럼 클라이언트의 요청에서 서로 약속한 HTTP API 스펙은 만족했지만, 그래서 비즈니스 로직까지 정상 수행했지만, 비즈니스 로직의 결과가 성공으로 처리되지 않을 수 있습니다. 이런 경우 400을 반환하게 되면, 클라이언트 개발자는 분명 서로 약속한 API 스펙을 다 지켰는데, 클라이언트 개발자에게 잘못 개발하셨어요. 라고 이야기하는 것과 같습니다.

그래서 이렇게 복잡한 비즈니스 로직이 들어가는 경우 200 OK로 응답하면서 결과에 내부 비즈니스 응답 코드를 정의해서 함께 전달합니다. 다음과 같이 응답 코드를 만들고 데이터도 한번 감싸서 전달하는 것이지요. 이것을 봉투 패턴(Envelope pattern)이라 합니다.

봉투가 포함된 응답

{

  "code": "success" ("success", "fail", "hold", "deny" ...)

  "data": {memberId: ... 결과 데이터}...

}

제가 선호하는 방식은 200 OK 라는 응답은 HTTP API 스펙상 요청이 성공해서 비즈니스 로직을 정상적으로 실행하는 단계까지 전달된 것으로 기준을 잡습니다. 결과적으로 HTTP 200 응답 코드는 비즈니스 로직의 내부 결과와는 무관하게 클라이언트와 서버간에 요청과 응답이 정상수행되었는지를 기준으로 잡습니다. HTTP 표준 스펙이 응답코드에 세상의 모든 비즈니스 로직까지 다 담을 수는 없으니까요.

물론 비즈니스 로직을 수행하다가 데이터베이스 접속이 안되거나 NullPointerException등이 발생하면, 이것은 500으로 응답을 하는 것이 맞습니다.

그런데 항상 이렇게 감싸면(이렇게 감싸는 것을 봉투 패턴(Envelope pattern) 이라 합니다.) 응답 구조가 너무 복잡해질 수 있습니다. 그래서 단순히 데이터만 조회하고, 크게 비즈니스 로직이 없는 경우에는 봉투 없이 단순하게 개발하는 것이 좋습니다.(특히 단순한 조회)

봉투 없는 단순한 응답

{

  memberId: ...

  username: ...

}

정리하면 다음과 같습니다.

1. 서로 약속한 HTTP API 스펙을 만족한다. -> 200, 만족하지 않는다. -> 400

2. 비즈니스 로직이 정상 수행되지만, 다양한 결과가 존재한다.(승인, 거절 등등) -> 200 + 비즈니스 코드 반환(봉투 패턴)

3. 비즈니스 로직을 수행하다가 내부에서 시스템 예외나 NullPointerException 등등 비즈니스와 관계없는 시스템 예외가 발생한다. -> 500

참고: 다양한 비즈니스 응답이 있는 복잡한 비즈니스 로직이 있는 HTTP API는 봉투 패턴을 고려하고, 비즈니스 로직이 거의 없는 단순한 조회에서는 봉투 패턴을 고려하지 않는다.

앞서 말씀드린 것 처럼 이것이 딱 정답이라기 보다는 제 경험에서 나온 부분이 많아서, 이것을 참고로 본인의 비즈니스에 맞는 HTTP API 스펙을 고민해보시면 좋겠습니다.

역시 강의 끝나고 다른 사람들이 질문한 것도 전부 읽어봐야 두 배로 공부되는 느낌!!! 💪

0개의 댓글