목차
불편함을 코드로 지웁니다.
개발 공부 노트, 개발 도구와 문제를 해결한 과정, AI 프롬프트와 Obsidian 활용법을 정리합니다.
HTTP 상태 코드는 RFC 9110 (HTTP Semantics)에 정의된 3자리 정수 응답 코드로, 서버가 클라이언트의 요청을 어떻게 처리했는지를 나타낸다.
상태 코드의 첫 번째 자릿수는 응답의 클래스(class) 를 구분하며, 총 5개의 클래스로 나뉜다.
| Class | 범위 | 의미 |
|---|---|---|
| 1xx | 100–199 | Informational — 요청 수신, 처리 계속 |
| 2xx | 200–299 | Successful — 요청 정상 처리 |
| 3xx | 300–399 | Redirection — 추가 동작 필요 |
| 4xx | 400–499 | Client Error — 클라이언트 측 오류 |
| 5xx | 500–599 | Server Error — 서버 측 오류 |
1. 1xx — Informational
| Code | Reason Phrase | 설명 |
|---|---|---|
| 100 | Continue | 클라이언트가 Expect: 100-continue 헤더와 함께 요청을 보낸 경우, 서버가 본문 전송을 계속해도 좋다고 알림 |
| 101 | Switching Protocols | Upgrade 헤더를 통한 프로토콜 전환 수락 (e.g. HTTP → WebSocket) |
| 102 | Processing | 요청 처리 중임을 알림 (WebDAV, RFC 2518) |
💡 포인트
- 100 Continue는 대용량 파일 업로드 시 불필요한 본문 전송을 방지하는 데 사용된다. 서버가 헤더만으로 요청을 거부할 수 있어 네트워크 비용을 절감할 수 있다.
- 101은 WebSocket 핸드셰이크에서 확인할 수 있다.
2. 2xx — Successful
| Code | Reason Phrase | 설명 | 주요 사용 메서드 |
|---|---|---|---|
| 200 | OK | 요청 성공. 응답 본문에 결과 포함 | GET, PUT, PATCH |
| 201 | Created | 리소스 생성 성공.Location 헤더에 새 리소스 URI 포함 권장 |
POST |
| 202 | Accepted | 요청 접수 완료, 비동기 처리 시작 | POST (비동기 작업) |
| 204 | No Content | 요청 성공, 응답 본문 없음 | DELETE, PUT |
💡 포인트
200 vs 201 vs 204 선택 기준
POST /api/users → 201 Created + Location: /api/users/42
GET /api/users/42 → 200 OK + Body
PUT /api/users/42 → 200 OK (본문 반환 시) 또는 204 No Content
DELETE /api/users/42 → 204 No Content
202 Accepted 활용 패턴
비동기 작업(이메일 발송, 대용량 CSV Export 등)에서는 202를 반환하고, 작업 상태를 조회할 수 있는 Polling Endpoint를 함께 제공하는 것이 일반적이다.
POST /api/reports/export → 202 Accepted
Location: /api/jobs/abc-123
GET /api/jobs/abc-123 → 200 OK
{ "status": "processing", "progress": 65 }
3. 3xx — Redirection
| Code | Reason Phrase | 영구/임시 | 메서드 변경 | 설명 |
|---|---|---|---|---|
| 301 | Moved Permanently | 영구 | ⚠️ 가능 | 리소스가 영구적으로 새 URI로 이동. 일부 클라이언트가 POST → GET으로 변경할 수 있음 |
| 302 | Found | 임시 | ⚠️ 가능 | 임시 리다이렉트. 마찬가지로 메서드 변경 가능성 있음 |
| 303 | See Other | — | ✅ GET 강제 | 리다이렉트 시 반드시 GET으로 변경 |
| 304 | Not Modified | — | — | 조건부 요청에서 캐시 유효함을 알림 |
| 307 | Temporary Redirect | 임시 | ❌ 유지 | 임시 리다이렉트. 메서드 변경 금지 |
| 308 | Permanent Redirect | 영구 | ❌ 유지 | 영구 리다이렉트. 메서드 변경 금지 |
💡 포인트
리다이렉트 코드 선택 플로우
영구 이동인가?
├── Yes → 메서드를 유지해야 하는가?
│ ├── Yes → 308 Permanent Redirect
│ └── No → 301 Moved Permanently
└── No → 메서드를 유지해야 하는가?
├── Yes → 307 Temporary Redirect
└── No → 302 Found (또는 303 See Other)
⚠️ 주의: 301/302는 역사적 이유로 일부 클라이언트가 POST를 GET으로 바꾸는 동작이 있다.
메서드 보존이 중요한 API에서는 307/308을 사용해야 한다.
304 Not Modified와 캐시 전략
Client → If-None-Match: "etag-value"
Server → 304 Not Modified (본문 없이 응답)
ETag / Last-Modified 기반의 조건부 요청은 API 응답 트래픽을 대폭 줄일 수 있다. CDN 환경에서는 필수적으로 고려해야 할 전략이다.
4. 4xx — Client Error
4-1. 요청 형식 오류
| Code | Reason Phrase | 설명 |
|---|---|---|
| 400 | Bad Request | 요청 구문 오류, 유효하지 않은 파라미터, 잘못된 JSON 형식 등 |
| 405 | Method Not Allowed | 해당 리소스에서 지원하지 않는 HTTP 메서드. Allow 헤더에 허용 메서드 목록 포함 필수 |
| 406 | Not Acceptable | Accept 헤더에 서버가 제공 불가한 미디어 타입만 명시된 경우 |
| 413 | Content Too Large | 요청 본문이 서버 허용 크기 초과 |
| 414 | URI Too Long | URI 길이 초과 |
| 415 | Unsupported Media Type | Content-Type이 서버가 처리할 수 없는 미디어 타입 |
| 422 | Unprocessable Content | 구문은 유효하나 비즈니스 로직상 처리 불가 (e.g. 이미 존재하는 이메일로 가입 시도) |
4-2. 인증 / 인가 오류
| Code | Reason Phrase | 설명 |
|---|---|---|
| 401 | Unauthorized | 인증(Authentication) 실패. 유효한 자격 증명이 없음 |
| 403 | Forbidden | 인가(Authorization) 실패. 인증은 완료되었으나 해당 리소스에 대한 권한 없음 |
⚠️ 401 vs 403: 네이밍이 혼동을 주지만, 401은 "너 누구야(인증)", 403은 "너인 건 알지만 안 돼(인가)"이다.
# 인증 실패
GET /api/admin/users
Authorization: (없음)
→ 401 Unauthorized
# 인가 실패
GET /api/admin/users
Authorization: Bearer <일반_유저_토큰>
→ 403 Forbidden
4-3. 리소스 상태 오류
| Code | Reason Phrase | 설명 |
|---|---|---|
| 404 | Not Found | 리소스가 존재하지 않음. 보안상 403 대신 404를 반환하기도 함 |
| 409 | Conflict | 리소스의 현재 상태와 충돌 (e.g. 동시 수정, 중복 생성) |
| 410 | Gone | 리소스가 영구적으로 삭제됨. 404와 달리 의도적 삭제를 명시 |
4-4. 기타
| Code | Reason Phrase | 설명 |
|---|---|---|
| 408 | Request Timeout | 서버가 대기 시간 내에 완전한 요청을 수신하지 못함 |
| 429 | Too Many Requests | Rate Limit 초과. Retry-After 헤더에 재시도 가능 시점 포함 권장 |
💡 포인트
400 vs 422 선택 기준
| 상황 | 적절한 코드 |
|---|---|
| JSON 파싱 실패, 필수 필드 누락 | 400 |
| 구문은 유효하나 비즈니스 규칙 위반 (e.g. 비밀번호 정책 미충족) | 422 |
429 응답 시 에러 바디 설계 예시
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "요청 한도를 초과했습니다. 60초 후 다시 시도해 주세요.",
"retryAfter": 60
}
}
보안 고려사항: 404 vs 403
리소스의 존재 여부 자체를 노출하고 싶지 않은 경우(e.g. 다른 사용자의 비공개 게시글), 403 대신 404를 반환하여 리소스 존재 자체를 숨기는 것이 일반적인 보안 패턴이다.
5. 5xx — Server Error
| Code | Reason Phrase | 설명 |
|---|---|---|
| 500 | Internal Server Error | 예상치 못한 서버 에러. catch되지 않은 예외 등 |
| 501 | Not Implemented | 서버가 해당 요청 메서드를 인식하지 못하거나 구현하지 않음 |
| 502 | Bad Gateway | 게이트웨이/프록시가 업스트림 서버로부터 잘못된 응답 수신 |
| 503 | Service Unavailable | 일시적 과부하 또는 점검 중. Retry-After 헤더 포함 권장 |
| 504 | Gateway Timeout | 게이트웨이/프록시가 업스트림 서버로부터 응답을 받지 못함 (타임아웃) |
💡 포인트
502 vs 504 트러블슈팅
[Client] → [Nginx (Reverse Proxy)] → [Application Server]
502: Nginx가 Application Server로부터 유효하지 않은 응답을 받음
→ App 크래시, 잘못된 응답 형식 등
504: Nginx가 Application Server의 응답을 시간 내에 받지 못함
→ 긴 처리 시간, App 행(hang), 네트워크 이슈 등
500 에러 응답 설계 원칙
프로덕션 환경에서 500 에러 발생 시, 내부 구현 세부사항(스택 트레이스, DB 쿼리 등)을 절대 클라이언트에 노출하지 않아야 한다.
// ❌ Bad — 내부 정보 노출
{
"error": "NullPointerException at UserService.java:142",
"query": "SELECT * FROM users WHERE id = 1"
}
// ✅ Good — 트래킹 ID만 제공
{
"error": {
"code": "INTERNAL_ERROR",
"message": "서버 내부 오류가 발생했습니다.",
"traceId": "abc-123-def-456"
}
}
6. API 에러 응답 표준화 — RFC 9457 (Problem Details)
REST API에서 에러 응답 형식이 제각각이면 클라이언트 측 에러 핸들링이 복잡해진다. RFC 9457은 이를 위한 표준 에러 응답 형식을 정의한다.
{
"type": "https://api.example.com/errors/insufficient-balance",
"title": "잔액이 부족합니다.",
"status": 422,
"detail": "현재 잔액은 1,000원이며, 요청 금액은 5,000원입니다.",
"instance": "/api/transactions/abc-123"
}
| 필드 | 설명 |
|---|---|
type |
에러 유형을 식별하는 URI (문서 링크로 활용 가능) |
title |
사람이 읽을 수 있는 에러 요약 |
status |
HTTP 상태 코드 |
detail |
에러에 대한 구체적 설명 |
instance |
에러가 발생한 구체적 리소스 경로 |
7. 정리 — 상태 코드 선택 플로차트
요청 처리 성공?
├── Yes
│ ├── 리소스 생성? → 201 Created
│ ├── 비동기 처리? → 202 Accepted
│ ├── 응답 본문 있음? → 200 OK
│ └── 응답 본문 없음? → 204 No Content
│
└── No
├── 클라이언트 잘못?
│ ├── 인증 실패? → 401 Unauthorized
│ ├── 인가 실패? → 403 Forbidden
│ ├── 리소스 없음? → 404 Not Found
│ ├── 구문 오류? → 400 Bad Request
│ ├── 비즈니스 규칙 위반? → 422 Unprocessable Content
│ ├── 리소스 충돌? → 409 Conflict
│ └── 요청 제한 초과? → 429 Too Many Requests
│
└── 서버 잘못?
├── 알 수 없는 오류? → 500 Internal Server Error
├── 기능 미구현? → 501 Not Implemented
├── 업스트림 잘못된 응답? → 502 Bad Gateway
├── 서버 과부하/점검? → 503 Service Unavailable
└── 업스트림 타임아웃? → 504 Gateway Timeout
References
- RFC 9110 — HTTP Semantics
- RFC 9457 — Problem Details for HTTP APIs
- MDN Web Docs — HTTP response status codes
- IANA HTTP Status Code Registry
'💡 Tech Note' 카테고리의 다른 글
| [Shortcut] PPT (0) | 2026.04.13 |
|---|---|
| Global Gitignore 설정 (0) | 2026.04.02 |
| 소프트웨어 개발 보안 핵심 가이드 (0) | 2026.02.27 |
| [서버 보안] API 키 관리 전략 및 환경변수 설정 (0) | 2026.02.27 |