HTTP 상태 코드와 에러 응답 설계
코드 선택 기준
| 코드 | 언제 쓰나 | 흔한 오용 |
|---|---|---|
| 200, 201, 204 | 성공, 생성, 본문 없음 | 실패인데 200 + 본문 에러 |
| 400 | 형식과 문법 오류 | 검증 실패를 전부 400으로 |
| 401, 403 | 미인증과 권한 없음 | 둘을 뒤섞음 |
| 404, 409 | 없음과 상태 충돌 | 중복 생성에 400 |
| 422 | 형식은 맞지만 처리 불가 | 400과 혼용 |
| 429 | 요청 한도 초과 | 503으로 대체 |
| 500, 503 | 서버 결함과 일시 과부하 | 클라이언트 잘못을 500으로 |
에러 본문 표준화
{
"type": "/errors/insufficient-balance",
"title": "잔액이 부족합니다",
"status": 409,
"detail": "요청 12000원, 잔액 3500원"
}
기계가 분기할 값(type)과 사람이 읽을 문장(title, detail)을 분리한다. 필드 검증 오류는 어느 필드가 왜 틀렸는지 배열로 함께 준다.
누구 잘못인지를 코드로 말한다
코드를 고르는 기준은 부르는 쪽이 다시 시도해도 되는가입니다.
| 상황 | 다시 시도하면 | 코드의 앞자리 |
|---|---|---|
| 요청이 잘못됐다 | 똑같이 실패한다 | 4로 시작 |
| 서버가 일시적으로 못 했다 | 성공할 수 있다 | 5로 시작 |
| 권한이 없다 | 로그인해도 안 될 수 있다 | 4로 시작하되 구분해서 |
셋째가 자주 뒤섞입니다. 로그인하지 않은 것과 로그인했지만 권한이 없는 것은 다른 응답이고, 앞은 로그인 화면으로 보내고 뒤는 보내면 안 됩니다. 같은 코드로 주면 무한 반복이 생깁니다.
본문이 코드보다 중요하다
코드는 분류이고, 무엇을 고쳐야 하는지는 본문이 말합니다.
기계가 분기할 수 있는 짧은 식별자를 넣는다
사람에게 보여 줄 문장은 별도 필드로 둔다
어느 필드가 왜 틀렸는지 목록으로 준다
둘째가 섞이면 곤란해집니다. 사람용 문장을 코드에서 비교하게 되고, 그러면 문구를 못 고칩니다. 그리고 서버 내부 사정은 본문에 담지 않습니다. 그것이 그대로 공격 정보가 됩니다.
실무 포인트
- 200 고정 + 본문 에러코드 방식은 프록시와 모니터링이 전부 성공으로 집계해 장애 감지를 늦춘다
- 5xx는 재시도 대상, 4xx는 재시도 무의미하다. 상태 코드가 클라이언트 재시도 정책의 입력값이다
- 권한 없는 리소스의 존재 여부를 숨기려면 403 대신 404를 주는 선택도 있다
- 내부 예외 메시지와 스택트레이스를 응답에 넣지 않는다