[HTTP]HTTP 상태코드

HTTP 상태코드란 클라이언트가 보낸 요청의 처리 상태를 응답에서 알려주기 위해 정의한 기능이다. 즉 클라이언트는 HTTP 상태코드를 보고 내가 보낸 요청이 어떻게 처리되었는지 알 수 있다. 따라서 서버는 요청 결과에 알맞은 상태코드를 보내줘야한다.

대략적인 의미

  • 1xx (Informational): 요청이 수신되어 처리중 (거의 사용하지 않음)
  • 2xx (Successful): 요청 정상 처리
  • 3xx (Redirection): 요청을 완료하려면 추가 행동이 필요
  • 4xx (Client Error): 클라이언트 오류, 잘못된 요청으로 서버가 요청을 수행할 수 없음
  • 5xx (Server Error): 서버 오류, 올바른 요청이나 서버 문제로 요청을 처리할 수 없음

클라이언트가 인식할 수 없는 상태코드를 서버가 반환한다면?

클라이언트는 상위 상태코드로 해석해서 처리한다. 미래에 새로운 생태 코드가 추가되어도 클라이언트를 변경하지 않아도 된다.

ex)

299 ??? -> 2xx(Successful)

451 ??? -> 4xx(Client Error)

2xx (Successful)

(1xx 코드는 거의 사용되지 않으므로 패스)

200번대 상태코드는 클라이언트의 요청을 성공적으로 처리했음을 의미한다.

자주 사용되는 코드

  • 200 OK
  • 201 Created
  • 202 Accepted
  • 204 No Content

200 OK - 요청 성공

예시 요청-응답

요청

GET /members/100 HTTP/1.1
Host: localhost:8080

응답

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 34

{
    "username": "awesomeo184",
    "age": 50
}

201 Created - 요청이 성공해 새로운 리소스가 생성됨

예시 요청-응답

요청

POST /members HTTP/1.1
Content-Type: application/json

{
    "username": "awesomeo184",
    "age": 50
}

응답

HTTP/1.1 201 Created
Content-Type: application/json
Content-Length: 34
Location: /members/100
{
    "username": "awesomeo184",
    "age": 50
}

생성된 리소스는 응답의 Location 헤더 필드로 식별


202 Accepted - 요청이 접수되었으나 처리가 완료되지 않았음

자주 사용되지는 않는다.

  • 배치 처리 같은 곳에서 사용
  • 예) 요청 접수 후 1시간 뒤 배치 프로세스가 요청을 처리함

204 No Content - 서버가 요청을 성공적으로 수행했지만, 응답 페이로드 본문에 보낼 데이터가 없음

클라이언트가 요청이 성공했다는 결과만 받으면 되는 경우, 결과 내용이 없어도 204 메시지만으로 성공을 인식할 수 있다.

예) 웹 문서 편집기에서 save 버튼

  • save 버튼의 결과로 아무 내용이 없어도 된다.
  • save 버튼을 눌러도 같은 화면을 유지해야한다.

3xx (Redirection)

300번대 코드는 요청을 완료하기 위해 유저 에이전트(주로 웹브라우저)의 추가 조치가 필요하다는 것을 의미한다.

자주 사용되는 코드

  • 301 Moved Permanently
  • 302 Found
  • 303 See Other
  • 304 Not Modified
  • 307 Temporary Redirection
  • 308 Permanent Redirection

Redirection

웹 브라우저는 3xx 응답 결과에 Location 헤더가 있으면, Location 위치로 자동 이동한다.

종류

  1. 영구 리다이렉션 - 특정 리소스의 URI가 영구적으로 이동한 경우

    • 예) /members -> /users (회원 목록의 리소스로 더이상 “/members”를 사용하지 않음)
  2. 일시 리다이렉션 - 일시적인 변경

    • 예) 주문 완료 후 주문 내역 화면으로 이동
  3. 특수 리다이렉션

    • 결과 리소스가 아니라 브라우저 캐시를 이용하는 경우

영구 리다이렉션

리소스의 URI가 영구적으로 이동되었음을 의미한다. 기존의 URL을 더이상 사용하지 않을 때 사용한다.

301 Moved Permanently

요청한 리소스가 Location 헤더에 주어진 URL로 완전히 변경되었음을 나타낸다. 브라우저는 이 페이지로 리다이렉트하고, 검색 엔진은 해당 리소스로 연결되는 링크를 갱신한다 명세에는 요청과 본문이 변하면 안된다고 되어있지만 이를 따르지 않는 서버가 많으므로 GET과 HEAD 메서드의 응답으로만 사용하고 POST의 경우 308 Permanent Redirect를 사용하는 것이 좋다.

308 Permanent Redirect

301과 기능은 같으나 리다이렉트시 요청 메서드와 본문이 유지되어야 함을 명시적으로 나타낸다. 예를 들어, 처음 POST를 보내면 리다이렉트도 POST로 유지되고 메시지 바디도 변경되지 않는다.

일시적인 리다이렉션

리소스의 URI가 일시적으로 변경된 경우 사용한다. 따라서 검색 엔진 등에서 URL을 변경하면 안된다. 실무에서 많이 쓰는 상태코드이다.

302 Found

301 Moved Permanently와 마찬가지로 명세에는 요청과 본문이 변하면 안된다고 되어있지만 요청 메서드가 GET으로 변하고 본문이 제거될 수 있다.

307 Temporary Redirect

302와 기능은 같으나 리다이렉트시 요청 메서드와 본문이 반드시 유지되어야 한다.

303 See Other

302와 기능은 같으나 요청 메서드가 반드시 GET으로 변경되어야 한다.

PRG: Post/Redirect/Get

PRG는 POST 요청이 중복되지 않도록 하는 디자인 패턴이다. POST 요청은 멱등하지 않기 때문에 중복 요청되어선 안된다.

예를 들어 상품을 주문하는 POST 요청을 보낸 뒤, 클라이언트에서 새로고침을 할 경우 서버에서 중복 요청을 막기위한 조치를 해놓지 않았다면 상품 요청이 두번 전달되는 참사가 일어날 수 있다. 이를 막기 위한 기본적인 디자인 패턴이 바로 PRG이다. 물론 결제같은 중요한 로직은 PRG 외에도 추가적으로 중복 요청을 막기 위한 조치들을 해주어야한다.

PRG는 매우 간단하다. 주문 로직을 예로 들면 다음과 같다.

  1. 클라이언트에서 POST로 주문 요청을 보낸다.
POST /order HTTP/1.1
Host: localhost:8080

itemId=mouse&count=1

  1. 서버는 주문 데이터를 생성한 후 GET 메서드로 리다이렉트한다.
HTTP/1.1 302 Found
Location: /order-result/19

  1. 클라이언트가 자동으로 리다이렉트한다.
GET /order-result/19 HTTP/1.1
Host: localhost:8080

  1. 서버에서 GET 요청에 대해 응답을 보낸다.
HTTP/1.1 200 OK

<html>주문완료</html>

이렇게하면 클라이언트에서 주문 이후 새로고침을 하더라도 서버에는 GET 요청이 전달되어 안전하다. 새로고침을 하면 마지막 요청을 다시 보내기 때문에, GET 요청으로 리다이렉트하지 않았을 경우 자신의 마지막 요청이었던 POST 요청을 다시 보내게 되어 주문 요청이 두 번 들어갈 수 있다.

기타 리다이렉션

304 Not Modified

캐시를 목적으로 사용한다.

클라이언트: 캐시가 만료된 것 같은데? 서버야 캐시좀 다시 줘

서버: 클라이언트야, 캐시 아직 만료 안됐어, 브라우저에 있는 캐시 그냥 써 (304 Not Modified)

304 응답은 “리소스가 수정되지 않았으니 로컬에 저장된 캐시를 써라”라는 의미를 전달한다. 따라서 클라이언트는 캐시로 리다이렉트하게 된다. 캐시를 쓰기때문에 응답에 메시지 바디를 포함해서는 안된다.

4xx (Client Error)

클라이언트의 요청에 문제가 있어 서버가 요청을 수행할 수 없을 때 사용한다.

클라이언트에서 이미 잘못된 요청을 하고 있기 때문에 같은 요청을 몇번이고 보내도 응답은 똑같다. 500번대 응답의 경우, 서버 문제로 요청이 처리되지 않는 것이기 때문에 같은 요청이라도 서버의 문제가 해결된다면 응답이 달라질 수 있다.

400 Bad Request

요청 구문, 메시지 등으로 서버가 요청을 처리할 수 없는 경우 사용한다. 클라이언트는 요청 내용을 다시 검토한 후 재요청해야한다.

401 Unauthorized

클라이언트가 해당 리소스에 대한 인증(Authentication)이 필요할 경우 사용한다. 401 오류 발생 시 WWW-Authenticate 헤더에 인증 방법에 대한 정보를 담아서 응답을 보낸다.

Authentication: 인증. 본인이 누군지 확인(ex. 로그인)

Authorization: 인가. 권한 부여(ex. admin, superuser)

401은 인증과 관련된 응답이지만 이름이 Unauthorized이다. 헷갈리지 않도록 주의

403 Forbidden

서버가 요청을 이해했지만 승인을 거부. 주로 인증 자격 증명은 있지만 접근 권한이 불충분한 경우 사용한다.

ex) 어드민 등급이 아닌 사용자가 로그인은 했지만, 어드민 등급의 리소스에 접근하는 경우.

404 Not Found

요청 리소스를 찾을 수 없음. 클라이언트가 요청한 리소스가 존재하지 않거나, 클라이언트가 권한이 부족한 리소스에 접근하는데 해당 리소스를 숨기고 싶을 때 사용한다.

5xx (Server Error)

클라이언트의 요청에는 문제가 없으나 서버 오류로 요청을 처리할 수 없음.

500 Internal Server Error

서버 문제로 오류 발생. 가장 범용적인 응답.

Service Unavailable

서비스 이용 불가. 서버가 일시적인 과부하 또는 예정된 작업으로 잠시 요청을 처리할 수 없음.

Retry-After 헤더 필드로 얼마 뒤에 복구되는지 보낼 수도 있다.

봉투 패턴(Envelope pattern)

김영한님 Q&A 답변 내용 정리

실무에서 어떤 응답 코드를 줘야할지 애매한 경우가 있을 수 있다.

예를 들어, 고객 정보가 20살 이상일 경우에만 통과하는 로직이 있는데 클라이언트에서 15살 고객의 정보가 넘어왔을 때, 어떤 상태코드로 응답해야할까.

일단 500은 아니다. 20살 미만을 걸러내는 것은 정상적인 비즈니스 로직이지 서버 에러와는 무관하기 때문이다.

그럼 4xx 응답을 줘야할까? 만약 클라이언트와 서버가 HTTP API 스펙을 정할 때, 20살 미만은 넘겨서는 안된다고 합의했다면 클라이언트가 약속을 지키지 않은 것이기 때문에 4xx 응답을 주는 것이 맞다.

그런데 클라이언트에서 HTTP API 스펙을 모두 준수했을 때 발생하는 비즈니스 예외들은 어떻게 처리해야 할까.

여러 방법이 있을 수 있는데 그 중 하나는 200 OK 응답을 주면서 메시지 바디에 결과를 전달하는 방법이다.

만약 비즈니스 로직이 복잡하다면 봉투 패턴으로 비즈니스 코드를 전달한다.

{
   "code": "fail" ("success", "fail", "hold", "deny" ...)
   "data": {memberId: ... 결과 데이터}...
}

만일 비즈니스 로직이 거의 없는 단순한 경우라면 봉투 없이 간단하게 전달한다.

{
  memberId: ...
  username: ...
}

참고 자료

김영한님 인프런 강의 - 모든 개발자를 위한 HTTP 웹 기본 지식

MDN documentation