Skip to content
HTTP 기초제 6장

상태 코드는 서버의 처리 결과와 다음 행동을 어떻게 알려줄까?

06. 상태 코드와 리다이렉션


학습 목표

  1. HTTP 상태 코드가 왜 필요한지, 응답 해석과 어떤 관련이 있는지 설명할 수 있다.
  2. 1xx, 2xx, 3xx, 4xx, 5xx의 큰 의미를 먼저 구분할 수 있다.
  3. 200, 201, 202, 204가 각각 어떤 성공 상황을 뜻하는지 설명할 수 있다.
  4. 301, 302, 303, 307, 308의 차이와 Location 헤더 역할을 이해할 수 있다.
  5. 400, 401, 403, 404, 500, 503를 어떤 기준으로 선택해야 하는지 설명할 수 있다.
  6. PRG(Post/Redirect/Get) 패턴이 왜 필요한지, 중복 POST를 어떻게 줄이는지 설명할 수 있다.

전체 구조


1. 상태 코드는 왜 필요할까?

클라이언트는 요청을 보낸 뒤, 서버가 그 요청을 어떻게 처리했는지 알아야 한다.

  • 정상 처리했는가?
  • 새 리소스를 만들었는가?
  • 다른 주소로 다시 가야 하는가?
  • 내 요청이 잘못되었는가?
  • 서버가 지금 고장 난 상태인가?

이 판단을 응답에서 가장 빠르게 전달하는 장치가 HTTP 상태 코드다.

응답 시작 라인을 떠올리면 이해가 쉽다.

http
HTTP/1.1 200 OK

여기서 200이 상태 코드다.
즉, 상태 코드는 서버가 클라이언트에게 보내는 결과 요약 신호라고 보면 된다.

전부 외우지 않아도 되는 이유

상태 코드는 종류가 많다.
하지만 처음부터 세부 숫자를 전부 외우려 하면 오히려 흐름을 놓치기 쉽다.

우선은 첫 번째 숫자만 보고 해석하는 습관이 중요하다.

예를 들어 세부 코드 299를 몰라도, 2xx 범주라는 사실만 보면 “성공 계열이구나”라고 해석할 수 있다.
이 방식은 미래에 새로운 세부 코드가 추가되더라도 큰 흐름을 유지하는 데 도움이 된다.

큰 분류 먼저 잡기

범주의미지금 단계에서의 이해
1xx정보성 응답요청을 받았고 처리 중
2xx성공요청을 성공적으로 처리
3xx리다이렉션추가 행동, 주로 다른 위치로 이동
4xx클라이언트 오류요청 자체가 잘못됨
5xx서버 오류서버 내부 문제로 처리 실패

이 장에서는 2xx, 3xx, 4xx, 5xx를 중심으로 본다.
1xx는 존재를 알아두는 정도면 충분하다.


2. 성공을 뜻하는 2xx

2xx는 “요청이 잘 처리되었다”는 범주다.
그런데 성공도 상황에 따라 의미가 달라서 몇 가지 대표 코드를 나눠 쓴다.

2-1. 200 OK

가장 기본적인 성공 응답이다.

  • 조회 성공
  • 일반적인 처리 성공
  • 특별한 추가 의미 없이 “잘 됐다”를 표현

예:

http
GET /members/100 HTTP/1.1

응답:

http
HTTP/1.1 200 OK
Content-Type: application/json

200 OK는 가장 자주 보게 되는 상태 코드다.

2-2. 201 Created

이 응답은 단순 성공이 아니라 새 리소스가 생성되었다는 뜻이다.

보통 POST로 등록 요청을 처리한 뒤 사용한다.

http
HTTP/1.1 201 Created
Location: /members/100

여기서 중요한 포인트는 Location 헤더다.

  • 서버가 새 리소스 URI를 만들었다
  • 그 URI를 클라이언트에게 알려준다

즉, 201은 “성공했어”에서 한 단계 더 나아가 “새 자원이 생겼고, 위치는 여기야”까지 알려준다.

2-3. 202 Accepted

이 코드는 요청이 접수되었지만 아직 처리 완료는 아니다라는 뜻이다.

예를 들어:

  • 대용량 파일 변환 요청
  • 비동기 배치 작업 등록
  • 오래 걸리는 처리 큐 적재

즉, 서버는 “요청은 받았고 나중에 처리할게”라고 말하는 셈이다.

이 때문에 202는 성공 응답이지만, 결과가 이미 끝났다고 해석하면 안 된다.

2-4. 204 No Content

이 코드는 성공했지만 응답 바디로 돌려줄 내용이 없다는 뜻이다.

예를 들어 웹 편집기에서 “저장” 버튼을 눌렀다고 해보자.

  • 저장은 성공했다
  • 하지만 굳이 새 데이터를 본문에 다시 보낼 필요는 없다

이럴 때 204가 자연스럽다.

핵심 직관: 200은 일반 성공, 201은 생성 성공, 202는 접수 성공, 204는 본문 없는 성공이다.

2xx 한눈에 보기

코드의미언제 자주 쓰나
200일반 성공조회, 일반 처리
201생성 성공신규 등록 후 URI 반환
202접수 완료, 처리 미완료비동기 작업, 배치 처리
204본문 없는 성공저장, 삭제 후 본문 불필요

3. 3xx: 다른 곳으로 다시 보라는 신호

3xx는 서버가 “이 응답만으로 끝이 아니고, 추가 동작이 필요하다”라고 알려주는 범주다.
가장 대표적인 형태가 리다이렉션이다.

리다이렉션에서 핵심은 Location 헤더다.

http
HTTP/1.1 301 Moved Permanently
Location: /new-event

이 응답을 받은 브라우저는 Location에 적힌 주소로 다시 요청한다.

3-1. 영구 리다이렉션: 301, 308

영구 리다이렉션은 “이 주소는 앞으로 더 이상 쓰지 말고, 새 주소를 써라”에 가깝다.

  • 301 Moved Permanently
  • 308 Permanent Redirect

예:

  • /event를 앞으로 없애고 /new-event만 사용
  • /members/users로 완전히 변경

검색 엔진도 이런 신호를 보고 새 주소를 더 중요하게 보기 시작할 수 있다.

301308의 차이

둘 다 “영구 이동”이라는 점은 같다.
차이는 리다이렉션 이후 메서드와 본문을 유지하느냐다.

  • 301: 오래된 사용자 에이전트나 관행 때문에 메서드가 GET으로 바뀌는 경우를 고려해야 한다
  • 308: 원래 메서드와 본문을 유지한다

즉, POST 요청을 영구 이동시키면서 메서드 보존이 중요하다면 308이 더 명확하다.

3-2. 일시 리다이렉션: 302, 303, 307

일시 리다이렉션은 “지금은 잠깐 이쪽으로 가라”에 가깝다.
원래 URI 자체를 버리라는 뜻은 아니다.

  • 302 Found
  • 303 See Other
  • 307 Temporary Redirect

예:

  • 주문 처리 후 결과 페이지로 잠깐 이동
  • 로그인 후 대시보드로 이동
  • 특정 시점에만 다른 화면으로 보냄

302, 303, 307의 차이

코드성격리다이렉션 후 메서드
302일시 이동많은 환경에서 GET으로 바뀜
303일시 이동반드시 GET으로 변경
307일시 이동원래 메서드 유지

여기서 중요한 기준은 단순하다.

  • 결과 화면을 다시 조회하게 만들고 싶다 → 303
  • 기존 메서드와 본문을 그대로 유지해야 한다 → 307
  • 관행적으로 프레임워크 기본값을 쓴다 → 302가 많이 쓰이기도 함

3-3. 304 Not Modified는 왜 여기서 깊게 다루지 않을까?

3043xx 계열이지만 성격이 조금 다르다.
이 코드는 “새 본문을 다시 보내지 않을 테니, 네 캐시를 계속 써라”에 가깝다.

즉, 일반적인 페이지 이동 리다이렉션보다는 캐시 재검증과 더 강하게 연결된다.
그래서 이 장에서는 이름만 알아두고, 자세한 설명은 캐시 장에서 따로 다룬다.


4. PRG: POST 뒤에 바로 GET으로 바꾸는 패턴

리다이렉션을 실무에서 가장 자주 체감하는 패턴 중 하나가 PRG(Post/Redirect/Get) 다.

문제: POST 결과 화면에서 새로고침하면?

사용자가 주문 폼을 제출했다고 해보자.

  1. POST /orders
  2. 서버가 주문을 생성
  3. 서버가 바로 주문 완료 HTML을 200 OK로 응답

겉보기에는 정상이다.
그런데 이 상태에서 사용자가 새로고침하면, 브라우저는 마지막 요청인 POST를 다시 보낼 수 있다.

그 결과:

  • 주문 중복 생성
  • 결제 중복 요청
  • 사용자 입장에서는 “새로고침만 했는데 왜 다시 처리되지?”라는 문제 발생

해결: POST 후 결과 페이지로 리다이렉트

PRG는 이 문제를 이렇게 푼다.

  1. 사용자는 여전히 POST /orders를 보낸다
  2. 서버는 주문을 생성한 뒤 302 또는 303으로 응답한다
  3. Location에 결과 조회용 URL을 넣는다
  4. 브라우저는 그 주소로 GET 요청을 보낸다
  5. 결과 화면은 GET 응답으로 보여준다

303이 특히 잘 맞을까?

303 See Other는 리다이렉션 후 반드시 GET으로 바꾸라는 의미가 명확하다.
그래서 PRG 의도와 아주 잘 맞는다.

다만 실무에서는 여전히 302를 기본값처럼 사용하는 프레임워크가 많다.
그렇기 때문에 “POST 뒤 결과 페이지로 보내는 흐름” 자체를 이해하는 것이 먼저고, 그 다음에 302303의 차이를 구분하면 된다.

핵심 직관: PRG는 “처리는 POST, 결과 화면은 GET”으로 분리해 새로고침 문제를 줄이는 패턴이다.


5. 4xx5xx는 무엇이 다를까?

이 둘은 모두 “오류”지만, 기준이 완전히 다르다.

  • 4xx: 클라이언트 요청 쪽 문제
  • 5xx: 서버 내부 문제

가장 실무적인 구분 기준은 이것이다.

같은 요청을 그대로 다시 보내면 계속 실패할 가능성이 높으면 4xx, 서버가 복구되면 같은 요청도 성공할 수 있으면 5xx에 가깝다.

5-1. 자주 보는 4xx

400 Bad Request

요청이 잘못되었다는 뜻이다.

예:

  • 요청 JSON 형식이 틀림
  • 필수 파라미터 누락
  • 타입이 맞지 않음
  • API 스펙 위반

핵심은 서버가 “네 요청 자체가 잘못됐어”라고 명확히 알려주는 것이다.
이런 문제를 500으로 보내면 클라이언트는 서버 장애로 오해할 수 있다.

401 Unauthorized

이 코드는 실제 의미상 인증이 필요하다에 가깝다.

즉:

  • 로그인하지 않았음
  • 인증 토큰이 없거나 잘못됨

그리고 이 응답에는 보통 어떤 인증이 필요한지 설명하는 헤더가 함께 간다.

http
WWW-Authenticate: Bearer

즉, 401은 “이 리소스는 인증이 필요해”라는 신호다.

403 Forbidden

요청은 이해했고, 인증도 되었을 수 있다.
하지만 권한이 부족해서 거부하는 경우다.

예:

  • 로그인은 했지만 관리자 권한이 없음
  • 본인 계정이 아닌 리소스에 접근

즉, 401이 “누구인지 증명해”라면, 403은 “누군지는 알겠지만 들어올 수는 없어”에 가깝다.

404 Not Found

요청한 리소스를 찾을 수 없다는 뜻이다.

예:

  • URL이 틀림
  • 해당 문서가 삭제됨
  • 존재하지 않는 API 경로 호출

실무에서는 보안상 이유로, 실제로는 권한이 없는 리소스인데도 존재 여부를 숨기기 위해 404를 쓰는 경우도 있다.

5-2. 자주 보는 5xx

500 Internal Server Error

서버 내부에서 예기치 않은 문제가 발생했다는 뜻이다.

예:

  • 처리 중 예외 발생
  • 코드 버그
  • 내부 로직 오류
  • DB 연결 문제 등으로 정상 처리 불가

이 코드는 정말 서버가 고장 났을 때 쓰는 것이 맞다.

비즈니스 규칙 위반까지 습관적으로 500으로 보내면 운영 판단이 흐려진다.

예를 들어:

  • 잔액 부족
  • 나이 제한 미충족
  • 이미 사용된 주문 번호

이런 것은 서버 장애가 아니라 정상적인 비즈니스 예외다.
즉, 웬만하면 400대나 도메인 규칙에 맞는 다른 응답으로 설계해야지, 500으로 던지면 안 된다.

503 Service Unavailable

이 코드는 서버가 지금은 일시적으로 처리할 수 없다는 뜻이다.

예:

  • 점검 중
  • 과부하 상태
  • 잠시 서비스 중단

이 경우 서버는 언제 다시 시도하면 좋을지 힌트를 줄 수도 있다.

http
Retry-After: 120

즉, “120초 뒤에 다시 시도해” 같은 신호를 줄 수 있다.

자주 쓰는 오류 코드 정리

코드의미핵심 해석
400잘못된 요청요청 스펙/형식 문제
401인증 필요로그인/인증 정보 부족
403접근 금지인증은 되었지만 권한 부족
404찾을 수 없음리소스가 없거나 숨김
500서버 내부 오류서버 쪽 예외/장애
503일시적 사용 불가점검, 과부하, 잠시 후 재시도

6. 상태 코드를 고를 때 실무 기준

상태 코드는 “적당히 성공”, “대충 오류”로 보내면 나중에 문제를 키우기 쉽다.
팀에서 아래 기준 정도는 공유해두는 편이 좋다.

  1. 성공은 200 하나로 몰아치기보다 201, 204처럼 의미가 분명한 코드를 함께 쓴다.
  2. 새 리소스를 만들었다면 201Location을 먼저 떠올린다.
  3. 결과 화면 이동은 리다이렉션으로 풀고, PRG가 필요한지 함께 본다.
  4. 클라이언트 입력/스펙 문제는 400대로 빠르게 돌려준다.
  5. 비즈니스 규칙 예외를 습관적으로 500으로 보내지 않는다.
  6. 서버가 진짜 일시 장애라면 503Retry-After를 고려한다.

핵심 암기 포인트

  • 상태 코드는 응답 결과를 요약해서 전달하는 신호다.
  • 세부 숫자를 다 외우기 전에 1xx, 2xx, 3xx, 4xx, 5xx의 큰 의미를 먼저 잡아야 한다.
  • 200은 일반 성공, 201은 생성 성공, 202는 접수 성공, 204는 본문 없는 성공이다.
  • 3xx는 보통 Location 헤더와 함께 동작하며, 브라우저가 그 위치로 다시 요청한다.
  • 303은 리다이렉션 후 GET으로 바꾸는 데 명확하고, 307/308은 원래 메서드를 유지한다.
  • PRG는 POST 처리 후 결과를 GET으로 보여줘서 새로고침에 의한 중복 제출을 줄인다.
  • 4xx는 클라이언트 요청 문제, 5xx는 서버 문제다.
  • 500은 서버 장애일 때만 써야 하고, 비즈니스 규칙 예외까지 무조건 500으로 보내면 안 된다.

확인 질문

  1. 왜 상태 코드는 세부 숫자보다 앞자리 범주를 먼저 이해하는 것이 중요할까?
  2. 200, 201, 202, 204는 각각 어떤 성공 상황을 뜻할까?
  3. 301308, 302303307은 무엇이 다를까?
  4. PRG를 적용하지 않으면 POST 결과 화면에서 어떤 문제가 생길 수 있을까?
  5. 401403의 차이는 무엇일까?
  6. 왜 잔액 부족 같은 비즈니스 예외를 500으로 보내면 안 될까?