도입
API를 만들다 보면 새로운 요구사항이 생길 때마다 헤더 하나를 추가하고 싶어집니다. 인증 정보, 디버그 플래그, 버전, 기능 토글, 실험용 옵션, 내부 추적 ID 같은 정보들이 대표적입니다.
하지만 HTTP 헤더는 단순한 문자열 슬롯이 아닙니다. 이름은 중간자와 캐시를 지나며 해석되고, 값 문법은 라이브러리와 프록시를 통과하며 조합되고, 잘못 설계하면 기존 표준 의미를 깨뜨리거나 보안 문제를 만들 수도 있습니다. 그래서 헤더 설계는 “일단 하나 만들어 쓰자”가 아니라, HTTP 위에서 오래 살아남을 수 있는 계약을 설계하는 일로 봐야 합니다.
필요성
HTTP는 헤더를 확장 지점으로 폭넓게 허용합니다. 그래서 헤더 하나를 추가하는 일 자체는 쉽습니다. 문제는 그 헤더가 다양한 클라이언트, 프록시, 캐시, 게이트웨이, 로깅 시스템을 모두 지나야 한다는 데 있습니다.
설계가 부실하면 이름 충돌, 파싱 실패, 캐시 오동작, 리다이렉트 시 민감 정보 유출, 중간자에 의한 변형 같은 문제가 생길 수 있습니다. 반대로 처음부터 기준을 잡고 설계하면, 새 헤더는 애플리케이션 확장을 돕는 좋은 도구가 됩니다.
- 사내 API나 외부 공개 API에 새 요청/응답 헤더를 추가할 때
- 기존 표준 헤더로는 부족해 보이는 기능을 확장할 때
- 프록시, CDN, API 게이트웨이, 브라우저 캐시를 함께 통과할 때
- 리다이렉트, 인증, A/B 테스트, 기능 협상 같은 제어 정보를 실어야 할 때
- 오래 유지될 공용 프로토콜이나 SDK를 설계할 때
정의
최근 HTTP 사양은 흔히 “헤더”라고 부르던 것을 더 넓게 field라고 부릅니다. 이유는 이름-값 쌍이 메시지의 header section뿐 아니라 trailer section에도 존재할 수 있기 때문입니다.
다만 실무와 문서에서는 여전히 “HTTP 헤더”라는 표현이 널리 쓰입니다. 그래서 이 글도 이해하기 쉽게 헤더라는 말을 계속 쓰지만, 엄밀히 말하면 설계 대상은 HTTP field라고 보는 편이 더 정확합니다.
핵심 원리
좋은 HTTP 확장은 기존 생태계를 존중해야 합니다. 기존 메서드, 상태 코드, 표준 헤더의 의미를 새 애플리케이션이 마음대로 덮어쓰면, 프록시와 캐시, 공용 클라이언트가 기대하는 동작이 깨집니다.
반대로 잘 설계된 헤더는 모르는 구현이 보더라도 전체 메시지를 망가뜨리지 않고, 아는 구현은 그 의미를 정확히 해석할 수 있습니다. 결국 헤더 설계의 핵심은 확장 가능성과 상호운용성입니다.
기본 구조
| 설계 항목 | 무엇을 정해야 하나 | 왜 중요한가 |
|---|---|---|
| 적합성 | 이 정보가 정말 헤더에 있어야 하는가 | 바디나 쿼리가 더 자연스러운 정보를 굳이 헤더로 만들지 않기 위해 |
| 이름 | 짧고 설명적인 이름인지, 기존 이름과 충돌하지 않는지 | 상호운용성과 가독성, 이름 공간 관리에 직결됨 |
| 값 문법 | 싱글턴인지 리스트인지, 반복 가능성은 어떤지 | 파싱과 결합 규칙이 애매하면 구현이 쉽게 갈라짐 |
| 전달 범위 | request/response/trailer 어디에서 유효한지 | 잘못된 위치에 등장했을 때 처리 규칙까지 정해야 함 |
| 중간자 | 프록시가 삽입/삭제/변경해도 되는지 | 실전 HTTP는 항상 중간자를 거칠 수 있기 때문 |
| 캐시 | 응답 선택에 영향을 주는지, Vary가 필요한지 | 잘못 설계하면 캐시가 틀린 응답을 재사용함 |
| 보안/개인정보 | 민감한 값인지, 리다이렉트 시 제거해야 하는지 | 유출, 오용, 로그 노출, 중간자 노출을 줄이기 위해 |
| 등록/문서화 | IANA 등록이 필요한지, 공개 문서가 있는지 | 공용 확장은 결국 문서와 레지스트리 위에서 오래 살아남음 |
패턴 1. 먼저 헤더가 정말 필요한가
모든 새 정보가 헤더에 적합한 것은 아닙니다. HTTP 기반 프로토콜 설계 지침은 새 헤더가 보통 세 경우에 특히 적합하다고 봅니다. 첫째, 프록시나 캐시 같은 중간자에게 유용한 정보일 때. 둘째, 클라이언트/서버 라이브러리 같은 일반적인 HTTP 소프트웨어가 알면 좋은 정보일 때. 셋째, 표현 형식 제약 때문에 그 값을 메시지 바디에 넣기 어려울 때입니다.
반대로 이 조건이 아니라면, 애플리케이션 전용 정보는 바디나 URL 쿼리 같은 다른 위치가 더 자연스러울 수 있습니다. 그리고 무엇보다 기존 HTTP 메서드, 상태 코드, 표준 헤더의 의미를 자기 애플리케이션 맥락으로 다시 정의해서는 안 됩니다.
헤더가 비교적 잘 맞는 정보
- 캐시나 프록시가 알아야 하는 메타데이터
- 인증, 콘텐츠 협상, 표현 선택 같은 전송 제어 정보
- 바디 형식이 담기 어려운 공통 제어값
헤더보다 바디/쿼리가 더 자연스러운 정보
- 순수 비즈니스 데이터
- 도메인 모델의 내부 속성
- 표현 본문과 강하게 결합된 내용
패턴 2. 이름은 어떻게 지을까
HTTP 필드 이름은 대소문자를 구분하지 않습니다. 하지만 상호운용성을 위해 새 이름은 가능하면 문자, 숫자, 하이픈, 점 정도로 제한하고, 첫 글자는 문자로 시작하는 편이 좋습니다. 특히 밑줄(_)은 일부 비HTTP 게이트웨이나 인프라에서 문제를 일으킬 수 있어 피하는 쪽이 안전합니다.
이름은 짧되 설명적이어야 합니다. 너무 일반적인 이름은 나중에 더 넓은 의미로 쓰일 가능성을 막아 버리고, 너무 긴 이름은 전송 오버헤드와 가독성을 해칩니다. 애플리케이션 전용 헤더라면 그 애플리케이션 식별자를 접두사처럼 붙이는 방식이 더 낫습니다. 예를 들어 Acme-Trace는 Trace보다 충돌 가능성이 낮고, X-Acme-Trace보다 현대적인 방향입니다.
좋지 않은 예
- X-User-Mode
- Description
- my_header_flag
더 나은 방향
- Acme-User-Mode
- Acme-Trace
- Example-Retry-After
X-는 예전에는 비표준 이름 공간을 표시하려는 관습이었지만, 실제 배포가 시작되면 표준 이름과 이중 호환 비용을 만드는 경우가 많아 현재는 새 이름에 권장되지 않습니다.패턴 3. 값 문법은 어떻게 정의할까
HTTP는 같은 필드 이름이 여러 번 등장할 수 있고, 일반적으로 반복된 필드 라인은 콤마로 결합된 하나의 값처럼 취급될 수 있습니다. 그래서 새 헤더를 만들 때는 이 필드가 리스트인지, 싱글턴인지, 반복이 가능한지, 그리고 중복이 들어오면 어떻게 처리할지를 반드시 문서화해야 합니다.
여기서 가장 흔한 함정이 콤마입니다. 값 안에 콤마가 자연스럽게 들어갈 수 있으면, 반복 필드 결합과 구분이 섞여 파싱이 곤란해집니다. 그래서 새 헤더는 값 문법을 ABNF로 엄격하게 정의하거나, 가능하면 Structured Fields를 사용하는 편이 더 안전합니다.
반복 필드 결합 예시
Example-Field: Foo, Bar
Example-Field: Baz
→ 결합된 값은 대체로 "Foo, Bar, Baz"처럼 해석될 수 있음
Structured Fields 스타일 예시
Acme-Retry: 30
Acme-Debug: ?1
Acme-Features: login, billing
Acme-Policy: mode="strict", retries=3
Structured Fields를 쓰면 값이 Item, List, Dictionary 같은 공통 타입 위에 올라가므로, 새 헤더마다 파서를 새로 설계하는 부담이 줄어듭니다. 특히 엄격한 파싱과 반복/콤마 처리 규칙을 통일하기 쉬워서 신규 헤더에 잘 맞습니다.
패턴 4. 중간자·캐시·보안까지 같이 설계하기
HTTP는 항상 종단 간 1:1 통신이 아닙니다. 프록시, CDN, 게이트웨이, 브라우저 캐시 같은 중간자가 필드를 보고 행동할 수 있습니다. 따라서 새 필드가 request에만 쓰이는지, response에만 쓰이는지, trailer에서도 허용되는지부터 문서에 명확히 써야 합니다.
또한 이 필드가 end-to-end인지 hop-by-hop인지 구분해야 합니다. hop-by-hop 필드라면 Connection 헤더와의 관계를 고려해야 하고, 그렇지 않다면 프록시가 모르는 필드라도 전달할 수 있게 설계되어야 합니다. 기본적으로 HTTP의 새 필드는 모르는 수신자가 안전하게 무시하거나 프록시가 전달할 수 있는 방향이 유리합니다.
캐시도 반드시 고려해야 합니다. 만약 어떤 request 헤더가 응답 내용 선택에 영향을 준다면, 그 자원은 Vary를 보내거나 아예 캐시 불가로 설계해야 할 수 있습니다. 그렇지 않으면 캐시가 다른 요청에 대해 잘못된 응답을 재사용할 수 있습니다.
보안도 중요합니다. 민감한 필드라면 프록시 로그나 중간자에 노출될 수 있고, 자동 리다이렉트 시 다른 origin으로 넘어가며 유출될 수 있습니다. 따라서 프라이버시 데이터, 인증 관련 상태, 보안 토큰류는 특히 주의해서 문서화해야 합니다.
- request 전용인가, response 전용인가, 둘 다 가능한가
- header section에서만 쓰는가, trailer도 허용하는가
- 프록시가 삽입·삭제·수정해도 되는가
- hop-by-hop인가, end-to-end인가
- 응답 선택에 영향을 준다면 Vary가 필요한가
- 리다이렉트 시 제거하거나 수정해야 하는가
- 개인정보나 인증 정보가 들어가는가
등록과 문서화
HTTP 필드 이름은 IANA의 HTTP Field Name Registry가 이름 공간을 관리합니다. 새 이름은 그 등록 절차를 통해 permanent, provisional, deprecated, obsoleted 같은 상태를 가질 수 있습니다.
실무 기준으로 보면, 사내 실험용 헤더를 당장 전부 등록해야 하는 것은 아닐 수 있습니다. 하지만 외부 공개 API, 공개 SDK, 여러 조직이 함께 쓰는 프로토콜이라면 최소한 문서화와 등록을 고려하는 편이 맞습니다. 결국 오래 살아남는 헤더는 코드보다 문서가 먼저 정리되어야 합니다.
한계와 주의점
헤더는 공용 메타데이터에 강하지만, 모든 정보를 헤더로 옮기는 순간 메시지 의미가 분산됩니다. 요청 본문에 있어야 할 도메인 데이터가 헤더로 쪼개지고, 캐시와 프록시 영향도 더 커집니다.
또한 헤더는 라이브러리와 인프라에서 자동 처리되는 경우가 많기 때문에, 애플리케이션 개발자가 직접 통제한다고 생각한 값이 중간에서 잘리거나 합쳐지거나 변형될 수도 있습니다. 그래서 새 헤더는 “쉽게 추가할 수 있다”보다 “추가한 뒤 오랫동안 책임질 수 있는가”를 먼저 따져야 합니다.
자주 하는 실수
- 기존 표준 헤더를 다른 의미로 재정의함
- 바디에 있어야 할 데이터를 습관적으로 헤더로 올림
- X- 접두사를 아직도 새 헤더 기본값처럼 씀
- 이름은 정했지만 값 문법과 반복 규칙을 문서화하지 않음
- 싱글턴 헤더인데 중복 발생 시 처리 규칙을 정하지 않음
- 캐시 영향이 있는 요청 헤더를 만들고도
Vary를 빠뜨림 - 민감한 필드를 리다이렉트나 로그 노출 경로까지 고려하지 않음
- 외부 공개 헤더인데 등록과 명세를 끝까지 미루고 사설 이름으로 버팀
실무 루틴
- 이 요구사항이 정말 새 헤더를 필요로 하는지 먼저 본다.
- 기존 메서드, 상태 코드, 표준 헤더, 바디, 쿼리로 풀 수 없는지 확인한다.
- 새 이름이 필요하다면 짧고 설명적으로 짓고
X-는 쓰지 않는다. - 값 문법을 명확히 정의하고, 가능하면 Structured Fields를 검토한다.
- 반복 가능성, 싱글턴, 중복 처리, 콤마 포함 값 규칙을 정한다.
- request/response/trailer, 중간자 변경 가능성, hop-by-hop 여부를 문서화한다.
- 캐시와
Vary, 리다이렉트 시 필드 처리, 보안/개인정보 영향을 검토한다. - 공개 확장이라면 IANA 등록과 장기 문서화를 고려한다.
디버깅
Vary 누락이나 캐시 정책 문제를 함께 본다.헤더 설계 문제를 볼 때 먼저 나눌 질문
- 이 정보가 정말 헤더에 있어야 하는가?
- 이름은 기존 표준과 충돌하지 않는가?
- 값 문법이 반복/콤마/싱글턴을 견디는가?
- 프록시와 캐시는 이 필드를 어떻게 다룰까?
- 보안상 리다이렉트나 로그에 노출되면 위험한가?
- 등록과 문서가 충분한가?
요약
- ✅ 새 헤더가 필요하기 전에 기존 표준, 바디, 쿼리로 해결 가능한지 먼저 본다.
- ✅ 기존 HTTP 메서드, 상태 코드, 표준 헤더의 의미를 재정의하지 않는다.
- ✅ 이름은 짧고 설명적으로 짓고, 너무 일반적인 단어와
X-접두사는 피한다. - ✅ 값 문법은 싱글턴/리스트/반복/콤마 처리까지 문서화한다.
- ✅ 가능하면 Structured Fields를 검토해 파싱 문제를 줄인다.
- ✅ request/response/trailer, 중간자, hop-by-hop 여부를 명확히 한다.
- ✅ 캐시에 영향이 있으면
Vary와 캐시 정책을 같이 설계한다. - ✅ 공개 확장이라면 IANA 등록과 장기 문서화를 고려한다.