HTTP 상태 코드
클라이언트가 보낸 요청의 처리 상태를 응답에서 알려주는 기능이다.
1xx (Informational): 요청이 수신되어 처리 중
2xx (Successful): 요청 정상 처리
3xx (Redirection): 요청을 완료하려면 추가 행동이 필요
4xx (Client Error): 클라이언트 오류, 잘못된 문법 등으로 서버가 요청을 수행할 수 없음
5xx (Server Error): 서버 오류, 서버가 정상 요청을 처리하지 못함
💡 모르는 상태 코드가 나온 경우 (클라이언트가 인식할 수 없는 상태코드를 서버가 반환한 경우) 클라이언트는 상위 상태코드로 해석해서 처리하므로 미래에 새로운 상태 코드가 추가되어도 클라이언트를 변경하지 않아도 된다. (ex. 299 → 2xx → Successful)
2xx - 성공
클라이언트의 요청을 성공적으로 처리했음을 나타낸다.
- 200 OK : 요청을 성공한 경우
[요청]
GET /members/100 HTTP/1.1
Host: localhost:8080
[응답]
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 34
{
"username": "young",
"age": 20
}
- 201 Created : 요청 성공으로 새로운 리소스가 생성되는 경우 생성된 리소스는 응답의 Location 헤더 필드로 식별한다
[요청]
POST /members HTTP/1.1
Content-Type: application/json
{ "username": "young", "age": 20 }
[응답]
HTTP/1.1 201 Created
Content-Type: application/json
Content-Length: 34
Location: /members/100 { "username": "young", "age": 20 }
- 202 Accepted : 요청이 접수되었으나 처리가 완료되지 않은 경우
- 배치 처리 같은 곳에서 사용한다. (ex. 요청 접수 후 1시간 뒤에 배치 프로세스가 요청을 처리하는 경우)
- 204 No Content : 서버가 요청을 성고적으로 수행했지만, 응답 페이로드 본문에 보낼 데이터가 없는 경우 (ex. 편집기의 저장 버튼) 결과 내용이 없어도 204 메시지(2xx)만으로 성공을 인식할 수 있다.
3xx - 리다이렉션
요청을 완료하기 위해 유저 에이전트(ex. 웹 브라우저)의 추가 조치가 필요한 경우
- 300 Multiple Choices
- 301 Moved Permanently
- 302 Found
- 303 See Other
- 304 Not Modified
- 307 Temporary Redirect
- 308 Permanent Redirect
리다이렉션
웹 브라우저는 3xx 응답의 결과에 Location 헤더가 있으면, Location 위치로 자동 이동(리다이렉트)한다.
1. 요청
GET /event HTTP/1.1
Host: localhost:8080
2. 응답
HTTP/1.1 301 Moved Permanently
Location: /new-event
3. 자동 리다이렉트
4. 요청
GET /new-event HTTP/1.1
Host: localhost:8080
5. 응답
HTTP/1.1 200 OK
리다이렉션 종류
- 영구 리다이렉션: 특정 리소스의 URI가 영구적으로 이동한다.
- 일시 리다이렉션: 일시적인 변경
- 주문 완료 후 주문 내역 화면으로 이동하는 경우
- PRG: Post/Redirect/Get
- 특수 리다이렉션: 결과 대신 캐시를 사용한다.
1. 영구 리다이렉션 (301, 308)
리소스의 URI가 영구적으로 이동했음을 알린다. 기존 URL은 더 이상 사용하지 않아야 하며, 검색 엔진도 이 변경 사항을 인지해 새 URL을 반영한다. 301 Moved Permanently 는 리다이렉트시 요청 메서드가 GET으로 변하고, 본문이 제거될 수 있다. 308 Permanent Redirect는 301과 기능은 같지만, 리다이렉트 시 요청 메서드와 본문을 유지한다. (ex. 처음 POST를 보내면 리다이렉트도 POST를 유지한다.)
👩🏻💻301 Moved Permanently
1. 요청 (POST 사용, 메시지 존재)
POST /event HTTP/1.1
Host: localhost:8080
name=hello&age=20
2. 응답
HTTP/1.1 301 Moved Permanently
Location: /new-event
3. 자동 리다이렉트
4. 요청 (GET으로 변경, 메시지 제거)
GET /new-event HTTP/1.1
Host: localhost:8080
5. 응답
HTTP/1.1 200 OK
...
👩🏻💻308 Permanent Redirect
1. 요청 (POST 사용, 메시지 존재)
POST /event HTTP/1.1
Host: localhost:8080
name=hello&age=20
2. 응답
HTTP/1.1 301 Moved Permanently
Location: /new-event
3. 자동 리다이렉트
4. 요청 (POST 유지, 메시지 유지)
GET /new-event HTTP/1.1
Host: localhost:8080
5. 응답
HTTP/1.1 200 OK
...
2. 일시적인 리다이렉션 (302, 307, 303)
리소스의 URI가 일시적으로 변경되었음을 알린다. 따라서 검색 엔진 등에서 기존 URL을 그대로 유지하며, 새 URL을 영구 주소로 취급하지 않는다.
302 Found는 리소스를 다른 URI로 임시 이동시킬 때 사용하며, 리다이렉트 과정에서 요청 메서드가 GET으로 바뀌고 본문이 제거될 수 있다.
307 Temporary Redirect는 302와 기능이 같고, 요청 메서드와 본문을 반드시 유지해야 한다. 따라서 클라이언트는 요청 메서드를 임의로 변경해서는 안 된다.
303 See Other는 302와 기능이 같고, 리다이렉트 시 요청 메서드를 GET으로 변경한다.
RPG: Post / Redirect / Get
사용자가 POST 요청으로 주문을 완료한 뒤 웹 브라우저를 새로고침하면, 같은 POST 요청이 다시 전송되어 중복 주문이 발생할 수 있다. 이를 방지하기 위해 PRG(Post/Redirect/Get) 패턴을 사용한다. 이 패턴에서는 주문이 성공하면 곧바로 결과 화면을 보여주는 대신, 서버가 결과 페이지의 URI로 리다이렉트(302/303 등) 를 보낸다. 그 결과 사용자는 주문 결과를 GET 요청으로 조회하게 되며, 이후 새로고침을 하더라도 동일한 GET 요청만 반복되므로 중복 주문이 발생하지 않는다.
PRG 이후 리다이렉트
- URL이 이미 POST → GET으로 리다이렉트 됨
- 새록 고침해도 GET으로 결과화면만 조회
🔎 PRG 사용 전
1. 요청 (url: /order)
POST /order HTTP/1.1
Host: localhost:8080
itemId=mouse&count=1
2. 주문 데이터 저장 (mouse 1개)
3. 응답 (url: /order)
HTTP/1.1 200 OK
<html>주문완료</html>
4. 결과 화면에서 새로고침 실수로 누름
5. 요청 다시 보냄
6. 주문데이터 중복 저장 (mouse 1개)
7. 응답 (url: /order)
HTTP/1.1 200 OK
<html>주문완료</html>
🔎 PRG 사용 후
PRG 패턴이 적용된 이후에는 사용자의 POST 요청이 이미 GET 요청으로 리다이렉트된 상태다. 따라서 사용자가 브라우저에서 새로고침을 하더라도 결과 화면을 GET 요청으로 다시 조회할 뿐이며, 중복 주문과 같은 문제가 발생하지 않는다.
1. 요청
POST /order HTTP/1.1
Host: localhost:8080
itemId=mouse&count=1
2. 주문 데이터 저장 (mouse 1개)
3. 응답
HTTP/1.1 302 Found
Location: /order-result/19
4. 자동 리다이렉트
5. 요청
GET /order-result/19 HTTP/1.1
Host: localhost:8080
6. 주문데이터 조회 (19번 주문)
7. 응답
HTTP/1.1 200 OK
<html>주문완료</html>
8. 결과화면(/order-result/19)에서 새로고침
결과화면만 다시 요청 (5.요청으로 이동)
/GET /order-result/19
302, 307, 303 중 어떤 것을 사용할지
302 Found -> GET으로 변할 수 있다
307 Temporary Redirect -> 메서드가 변하면 안 된다
303 See Other -> 메서드가 GET으로 변경
원래 302 Found 스펙의 의도는 HTTP 메서드를 그대로 유지하는 것이었다. 그러나 대부분의 웹 브라우저가 리다이렉트 시 요청 메서드를 자동으로 GET으로 바꾸었고, 일부 브라우저는 다르게 동작하기도 했다. 이러한 모호함을 해결하기 위해 302를 대신할 명확한 307 Temporary Redirect와 303 See Other가 등장했으며, 301 Moved Permanently 대응으로 308 Permanent Redirect도 추가되었다.
현실적으로는 307과 303이 권장되지만, 이미 많은 애플리케이션 라이브러리들이 302를 기본값으로 사용하고 있다. 또한 자동 리다이렉션 시 요청 메서드가 GET으로 변해도 문제가 없는 경우에는 302를 그대로 사용해도 문제 없다.
3. 기타 리다이렉션 (300, 304)
300 Multiple Choices는 거의 사용되지 않는다. 304 Not Modified는 주로 캐시를 목적으로 사용된다. 서버는 클라이언트에게 리소스가 수정되지 않았음을 알려주며, 클라이언트는 로컬 PC에 저장된 캐시를 그대로 재사용한다. 따라서 304 응답에는 메시지 바디를 포함하면 안 된다. 주로 조건부 GET이나 HEAD 요청에 사용된다.
4xx (Client Error)
클라이언트의 요청이 잘못된 문법을 포함하거나 불완전하여 서버가 요청을 수행할 수 없는 경우가 있다. 이때 오류의 원인은 클라이언트에 있다. 🌟 클라이언트가 이미 잘못된 요청이나 데이터를 보내고 있기 때문에 같은 요청을 다시 시도해도 실패한다.
📌 400 Bad Request
클라이언트가 잘못된 요청을 보내 서버가 요청을 처리할 수 없는 상태다. 요청 구문이나 메시지 등에 오류가 있을 때 발생하며, 클라이언트는 요청 내용을 다시 검토하고 올바르게 보내야 한다. 예를 들어 요청 파라미터가 잘못되었거나 API 스펙과 맞지 않을 때 400 오류가 발생한다.
📌 401 Unauthorized
클라이언트가 해당 리소스에 접근하기 위해 인증(Authentication)이 필요한 상태다. 인증되지 않은 요청이 들어오면 401 오류가 발생하며, 응답에는 WWW-Authenticate 헤더와 함께 인증 방법이 안내된다. 인증은 사용자가 누구인지 확인하는 과정이고, 인가(Authorization)는 특정 리소스에 접근할 수 있는 권한을 부여하는 과정이다. 오류 메시지가 Unauthorized라고 되어 있지만, 실제 의미는 인증되지 않았음을 뜻한다.
📌 403 Forbidden
서버는 클라이언트의 요청을 이해했지만 접근을 거부한 상태다. 주로 인증은 되어 있지만 권한이 불충분할 때 발생한다. 예를 들어 일반 사용자가 로그인은 했지만 어드민 전용 리소스에 접근하려 할 때 403 오류가 발생한다.
📌 404 Not Found
클라이언트가 요청한 리소스를 서버에서 찾을 수 없는 상태다. URL이 잘못되었거나, 리소스가 삭제되었을 때 발생한다.
5xx (Server Error)
서버 문제로 인해 오류가 발생했음을 나타낸다. 서버 자체의 문제이므로, 클라이언트가 같은 요청을 재시도하면 서버가 복구되는 등의 이유로 성공할 가능성이 있다. 웬만해서는 서버에서 500번대 오류를 임의로 생성하면 안 되며, 실제로 서버에 문제가 발생했을 때만 500번 오류를 사용해야 한다.
📌 500 Internal Server Error
서버 내부에서 문제가 발생하여 요청을 처리할 수 없는 상태다. 문제가 명확하지 않거나 애매한 경우에도 500 오류를 사용한다.
📌 503 Service Unavailable
서버가 일시적인 과부하나 예정된 작업 등으로 인해 요청을 처리할 수 없는 상태다. 이때 서버는 Retry-After 헤더를 통해 얼마 뒤에 서비스가 복구될지를 클라이언트에 안내할 수 있다.