이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- HTTP request line, status line, header, body를 API 계약과 장애 증거로 어떻게 읽는가?
- proxy, gateway, application이 각각 어떤 header를 추가하거나 바꾸는가?
- 백엔드 API의 성공/실패 응답을 운영자가 추적 가능한 형태로 설계하려면 무엇이 필요한가?
개요
HTTP 메시지는 start line, header, body로 구성된다. 백엔드 개발자에게 HTTP 메시지는 JSON을 담는 봉투가 아니라 인증, 라우팅, 캐시, 추적, 오류 의미가 들어 있는 운영 계약이다.
- HTTP 요청과 응답은 start line, header, body로 나뉜다.
- 상태 코드는 서버의 처리 결과를 계층적으로 표현한다.
- 백엔드는 request id, content type, authorization, cache header를 API 계약의 일부로 봐야 한다.
왜 백엔드 개발자가 알아야 하는가
장애 대응에서 필요한 증거는 body보다 header와 start line에 있을 때가 많다. 같은 endpoint라도 method, Host, Authorization, Content-Type, Accept, Trace header가 다르면 완전히 다른 요청이다.
Host와 path가 다르면 다른 virtual host나 route로 간다.Content-Type이 틀리면 JSON body가 있어도 binding이 실패할 수 있다.Authorization이 누락되었는지, gateway에서 제거되었는지, app이 거절했는지를 구분해야 한다.X-Request-ID나traceparent가 없으면 proxy와 application log를 연결하기 어렵다.- error body가 제각각이면 on-call과 client 개발자가 원인을 빠르게 분류하지 못한다.
요청 흐름에서의 위치
HTTP 메시지는 proxy, gateway, app, client가 모두 해석하는 공통 언어다.
request line: POST /orders?dryRun=false HTTP/1.1
headers: Host, Authorization, Content-Type, Accept, traceparent
body: JSON, form, multipart, stream
status line: HTTP/1.1 201 Created
headers: Location, Cache-Control, Content-Type, X-Request-ID
body: representation or error detailproxy는 forwarding header를 붙이고, gateway는 auth나 rate limit 결과를 만들 수 있고, application은 business status와 body를 만든다. 누가 응답을 만들었는지 구분해야 한다.
원리
요청 메시지의 기본 요소는 다음과 같다.
| 요소 | 예시 | 실무 의미 |
|---|---|---|
| Method | GET, POST, PUT, DELETE | 안전성, 멱등성, cache, retry 판단 |
| Request target | /orders?status=paid | routing, query binding, cache key |
| Version | HTTP/1.1, HTTP/2 | framing, connection, proxy 동작 |
| Header | Content-Type, Authorization | 인증, body 해석, negotiation, tracing |
| Body | JSON, multipart, stream | domain payload, size, validation |
응답 메시지는 status code, header, body가 함께 의미를 가진다. 404와 {"code":"ORDER_NOT_FOUND"}가 함께 있어야 client와 운영자가 같은 결론에 도달한다.
핵심 판단 기준
- API 계약은 body schema만이 아니라 method, path, status, header, error body까지 포함한다.
- 실패 응답은 사람이 읽는 message보다 기계가 분류할 수 있는
code,requestId,timestamp가 중요하다. Server,Via,X-Cache,X-Request-ID,traceparent는 응답 주체와 경로를 찾는 단서다.- hop-by-hop 성격의 header와 end-to-end header를 구분한다. proxy가 넘기면 안 되는 값이 있다.
- 민감한 header와 body는 로그에 남기지 않는다. 특히 Authorization, Cookie, Set-Cookie, token, 개인정보는 mask 기준이 필요하다.
실무에서 자주 만나는 문제
- header 누락 때문에 인증이나 CORS가 실패한다.
- response body만 보고 status code 의미를 놓친다.
- error body 형식이 API마다 달라 운영 대응이 느려진다.
- client가
Content-Type: text/plain으로 JSON을 보내 Spring binding이 실패한다. - gateway가
Authorization을 제거하거나 다른 header로 전달하는데 app 팀이 모른다. - proxy가
X-Forwarded-Proto를 주지 않아 app이 http redirect URL을 만든다. - health check는 200인데 dependency 장애를 숨겨 실제 요청은 500을 낸다.
- error response에 request id가 없어 client 제보와 server log를 연결하지 못한다.
HTTP 메시지 문제는 “코드는 같은데 특정 client만 실패”로 자주 나타난다. 성공 요청과 실패 요청의 method/path/header/body/status를 나란히 비교해야 한다.
디버깅 방법
curl -v로 request header와 response header를 본다.--include로 body와 header를 함께 남긴다.- 성공/실패 요청의
Content-Type,Accept,Authorization,Cookie,X-Request-ID를 비교한다. - proxy access log와 app access log에서 같은 request id를 찾는다.
- error body가 RFC 9457 Problem Details 형식인지, 내부 예외 stack을 노출하지 않는지 본다.
curl -v --include https://api.example.com/orders/123
curl -v --include -X POST https://api.example.com/orders \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'X-Request-ID: local-test-001' \
-d '{"productId":10,"quantity":1}'확인할 것은 status 하나가 아니다. request id가 되돌아오는지, Content-Type이 맞는지, Location이 필요한 응답에 있는지, cache 관련 header가 의도대로인지 본다.
코드로 확인하기
Spring Boot에서는 오류 응답도 API 계약으로 만든다.
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
class ApiExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
ProblemDetail badRequest(IllegalArgumentException ex) {
ProblemDetail detail = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
detail.setTitle("Invalid request");
detail.setDetail(ex.getMessage());
detail.setProperty("code", "INVALID_REQUEST");
return detail;
}
}여기에 request id filter나 tracing이 연결되어야 운영자가 client의 오류 응답과 server log를 빠르게 이어 볼 수 있다.
주니어가 자주 하는 오해
- HTTP body만 API 계약이라고 생각한다.
- method, path, status, header, error format도 계약이다.
- status code와 error code가 둘 중 하나만 있으면 된다고 생각한다.
- status는 HTTP 의미, error code는 서비스 도메인 의미를 담는다.
- header는 proxy가 알아서 넘긴다고 생각한다.
- 일부 header는 제거되거나 새로 만들어지고, 신뢰 경계가 필요하다.
- 로그에 요청 전체를 남기면 디버깅에 좋다고 생각한다.
- Authorization, Cookie, 개인정보가 유출될 수 있다.
시니어의 설계 판단 기준
- API 표준에 success body, error body, request id, trace id, pagination, idempotency header를 명시한다.
- 운영자와 client가 같은 오류를 같은 방식으로 분류하도록 error code catalog를 둔다.
- proxy/gateway가 추가하는 header와 app이 신뢰하는 header를 문서화한다.
- 로그 정책은 디버깅 가능성과 개인정보/비밀 보호 사이에서 정한다.
- 상태 코드만으로 비즈니스 상태를 과하게 표현하지 않고 body의 domain code와 함께 쓴다.
인프라 협업 포인트
- gateway가 어떤 header를 추가/제거하는지, request id를 어디에서 생성하는지 합의한다.
- WAF나 gateway가 만든 403/429/502와 app이 만든 응답을 구분할 수 있는 header/log를 둔다.
- 장애 제보에는 full URL, method, request header subset, response header, request id, body size를 포함한다.
- 인프라 팀은 header 하나가 routing, auth, caching, tracing을 동시에 바꿀 수 있어 변경 검토가 까다롭다.
실전 팁
- 문서화할 때 예시 request와 response를 둘 다 둔다.
- error response에는
code,message/detail,requestId,timestamp를 포함한다. - response header는 기능 요구사항이다.
Cache-Control,Location,Retry-After,Vary를 빼먹지 않는다. - access log에는 method, path, status, latency, request id, user agent 정도를 남기고 민감 값은 mask한다.
위험 신호!
- error body 형식이 API마다 다르다.
- request id가 응답과 로그에 함께 남지 않는다.
- gateway가 어떤 header를 넘기는지 앱 팀이 모른다.
Content-Type없이 body를 받거나, 잘못된 body를 조용히 허용한다.- 오류 응답에 내부 class name이나 stack trace가 노출된다.
확인 질문
확인 질문
- HTTP API 계약에 body 외에 무엇이 포함되어야 하는가?
- method, path, status code, request/response header, content type, error body, tracing/request id가 포함되어야 한다.
- request id가 중요한 이유는 무엇인가?
- client가 본 응답과 proxy/application log를 같은 요청으로 연결해 장애 원인을 좁힐 수 있기 때문이다.
- proxy가 만든 403과 app이 만든 403을 구분하려면 무엇을 봐야 하는가?
- response header, body 형식, proxy/WAF log, app access log 도달 여부, request id 연결 여부를 봐야 한다.