콘텐츠 협상
고친 사람 github-actions[bot]
콘텐츠 협상은 같은 주소에 여러 형태로 준비해 둔 응답 중 하나를 골라 줍니다. 받는 쪽이 원하는 형식과 언어를 미리 알려 주면, 주는 쪽이 그에 맞춰 하나를 내놓습니다. 그래서 형식마다 주소를 따로 만들지 않고도 상대에 맞는 응답을 돌려줄 수 있습니다.
쉽고 빠른 이해
콘텐츠 협상은 「나는 이런 걸 받고 싶다」는 받는 쪽의 희망을 듣고 주는 쪽이 하나를 골라 주는 방식입니다. 같은 주소가 브라우저에는 화면용 문서를, 프로그램에는 데이터 덩이를 돌려주는 것이 그런 예입니다.
이게 없으면 형식과 언어마다 주소를 따로 만들어야 합니다. 그러면 같은 내용이 여러 주소로 갈라지고, 어느 주소를 눌러야 하는지 독자가 직접 골라야 합니다.
어떻게 도는가:
- 받는 쪽이 원하는 형식과 언어를 요청에 적어 보냅니다
- 주는 쪽은 자기가 가진 것들과 그 희망을 맞춰 보고 하나를 고릅니다
- 고른 것을 보내면서 무엇을 골랐는지와 무엇을 보고 갈랐는지를 함께 알립니다
대가가 있습니다. 같은 주소인데 응답이 달라지므로 중간에서 응답을 보관하는 쪽이 헷갈립니다. 준비해 둘 형태가 여러 벌로 늘고, 희망을 적어 보낸 목록이 그 사람을 알아보는 단서로도 쓰입니다.
상세
택배를 시킬 때마다 주문서에는 「부재 시 경비실에 맡겨 주세요」 같은 배송 메모가 저절로 따라붙습니다. 언제 한 번 저장해 두고 잊은 문장입니다. 기사는 그 줄을 읽고 문 앞과 경비실과 무인 택배함 가운데 하나를 골라 둡니다.
콘텐츠 협상은 주소 하나에 여러 형태의 응답을 준비해 두고, 요청에 적힌 희망을 보고 그중 하나를 골라 보내는 방식입니다. 같은 안내 문서를 한국어판과 영어판으로 함께 두고 요청마다 맞는 쪽을 내주는 것이 그런 예입니다.
이 방식은 HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 규약)에서 주로 만납니다. 웹에서 주고받는 요청과 응답이 이 희망 목록을 실어 나릅니다.
이렇게 고르는 절차를 두지 않으면 형식과 언어마다 주소를 따로 만들어야 합니다. 같은 내용이 여러 주소로 갈라지면 어느 주소를 알려야 할지 매번 정해야 하고, 링크를 건 쪽도 상대가 무엇을 원하는지 미리 알 수 없습니다.
주소 하나에 표현 여럿
고를 것이 있으려면 먼저 여러 벌이 있어야 합니다. 이 소절은 그 여러 벌이 무엇인지를 봅니다.
자원은 주소가 가리키는 대상입니다. 「이번 달 요금 안내」처럼 뜻으로 정해지고, 바이트 모양은 아직 안 정해졌습니다.
표현은 그 자원을 실제 바이트로 적어 놓은 한 벌입니다. 같은 요금 안내라도 화면용 문서로 적은 것과 데이터 덩이로 적은 것은 서로 다른 표현입니다. 한국어로 적은 것과 영어로 적은 것도 마찬가지입니다.
flowchart TD
R["자원 · 이번 달 요금 안내"]
subgraph 표현
A["화면용 문서 · 한국어"]
B["화면용 문서 · 영어"]
C["데이터 덩이 · 한국어"]
end
R --> A
R --> B
R --> C
그림에서 위는 하나인데 아래는 셋입니다. 표현이 하나뿐이면 협상할 것이 없고, 이렇게 여럿일 때만 고르는 일이 생깁니다.
고르는 잣대 넷
받는 쪽이 아무 말이나 할 수 있는 것은 아닙니다. 희망을 적을 수 있는 축이 정해져 있고, 축마다 요청에 적는 헤더가 따로 있습니다.
| 잣대 | 요청에 적는 헤더 | 무엇이 갈리나 |
|---|---|---|
| 형식 | Accept | 화면용 문서냐 데이터 덩이냐 |
| 언어 | Accept-Language | 한국어냐 영어냐 |
| 압축 방식 | Accept-Encoding | 눌러서 보내느냐 그대로 보내느냐 |
| 문자 인코딩 | Accept-Charset | 글자를 어느 규칙으로 바이트에 담느냐 |
네 축은 서로 독립입니다. 형식은 화면용 문서로 정하면서 언어는 영어로 갈 수 있습니다. 맨 아래 문자 인코딩은 쓰임이 거의 사라졌습니다 — 요즘 웹이 쓰는 인코딩이 하나로 굳어서 고를 것이 남지 않았습니다.
선호도를 적는 법
희망은 하나만 적는 것이 아닙니다. 여럿을 적고 그중 무엇을 더 원하는지까지 나타낼 수 있습니다.
값 뒤에 붙이는 품질값이 그 순위를 맡습니다. 0 과 1 사이의 숫자이고, 클수록 더 원한다는 뜻입니다. 아무것도 안 붙이면 가장 원하는 것으로 칩니다.
ko // 한국어만 받는다
ko, en // 둘 다 되고 한국어 먼저
ko, en;q=0.7 // 영어는 차선이다
세 줄은 모두 언어 희망을 적은 것인데 뜻이 다릅니다. 첫 줄은 한국어가 없으면 받을 것이 없고, 아래 두 줄은 영어라도 받겠다는 말입니다.
고르는 주체가 갈리는 두 갈래
누가 고르느냐로 갈래가 둘입니다. 이 소절은 그 둘이 왕복 수에서 어떻게 갈리는지를 봅니다.
서버가 고르는 갈래에서는 요청에 적힌 희망을 보고 주는 쪽이 바로 하나를 정합니다. 왕복이 한 번이라 빠르고, 웹에서 만나는 협상은 대개 이쪽입니다.
받는 쪽이 고르는 갈래에서는 주는 쪽이 가진 목록을 먼저 내놓습니다. 받는 쪽이 그 목록에서 하나를 골라 다시 요청합니다. 고르는 사람의 뜻이 정확히 반영되지만 왕복이 두 번으로 늘어서 잘 쓰이지 않습니다.
sequenceDiagram
participant 받는쪽
participant 주는쪽
받는쪽->>주는쪽: 원하는 형식과 언어를 적어 보낸다
Note over 주는쪽: 가진 표현들과 맞춰 보고 하나를 고른다
주는쪽-->>받는쪽: 고른 표현 · 무엇을 보고 갈랐는지
Note over 받는쪽,주는쪽: 목록을 먼저 받아 직접 고르는 갈래도 있다
그림은 서버가 고르는 갈래를 그린 것입니다. 오가는 것이 한 왕복뿐이고, 고르는 일은 화살표 사이에서 끝납니다.
고른 결과를 알리는 응답 헤더
고르고 끝내면 받는 쪽은 무엇을 받았는지 모릅니다. 그래서 응답에도 고른 결과를 적어 보냅니다.
Content-Type은 고른 형식을, Content-Language는 고른 언어를 알립니다. 여기에 더해 Vary가 「이 응답은 요청의 어느 헤더를 보고 갈렸는가」를 알립니다.
요청
Accept: text/html // 원하는 형식
Accept-Language: ko // 원하는 언어
응답
Content-Type: text/html // 골라 준 형식
Content-Language: ko // 골라 준 언어
Vary: Accept-Language // 갈린 잣대
맨 아랫줄이 협상에만 있는 것입니다. 같은 주소인데 응답이 갈릴 수 있다는 사실을 중간에 끼어 있는 캐시에게 알려 주는 한 줄입니다.
캐시는 보통 주소만 보고 사본을 꺼내 줍니다. 이 한 줄이 없으면 한국어를 원한 사람에게 저장해 둔 영어판이 그대로 나갑니다. 무엇을 보고 갈렸는지 알려 주면 캐시는 그 헤더 값까지 함께 보고 사본을 고릅니다.
쓰는 때와 안 쓰는 때
협상은 공짜가 아니므로 늘 켜 두는 것이 아닙니다. 이 소절은 켤 만한 조건과 끄는 편이 나은 조건을 갈라 봅니다.
켤 만한 때는 같은 내용을 여러 벌로 내주면서 주소는 하나로 두고 싶을 때입니다. 다국어 안내 문서나, 사람과 프로그램이 함께 읽는 응답이 그런 예입니다. 압축 방식을 고르는 협상은 거의 모든 응답에 켜 둡니다 — 표현이 늘지 않고 같은 내용을 눌러서 보낼 뿐이라 대가가 작습니다.
끄는 편이 나은 때는 표현이 처음부터 하나뿐일 때입니다. 고를 것이 없으면 갈림을 알리는 한 줄만 늘어 캐시를 쪼갭니다. 사람이 언어를 직접 고르게 하는 서비스도 마찬가지입니다 — 주소에 언어를 적어 나누면 어느 판을 보고 있는지 눈에 보이고, 다른 사람에게 그 주소를 그대로 건넬 수 있습니다.
콘텐츠 협상이 지는 대가
첫째, 캐시가 어려워집니다. 갈림을 알리는 헤더가 하나 늘 때마다 사본이 갈래별로 쪼개지고, 그만큼 저장해 둔 것을 다시 쓸 확률이 떨어집니다. 요청마다 값이 제각각인 헤더를 잣대로 쓰면 사본이 거의 안 재사용됩니다.
둘째, 준비할 것이 늘어납니다. 형식 둘과 언어 셋을 지원하면 관리할 표현이 여섯 벌이 됩니다. 한 벌을 고치고 나머지를 안 고치면 같은 주소가 서로 다른 내용을 내놓습니다.
셋째, 희망 목록 자체가 그 사람을 알아보는 단서가 됩니다. 어떤 언어를 어떤 순서로 적었는지가 사람마다 조금씩 달라서, 이름을 안 밝혀도 같은 사람을 다시 알아볼 재료가 됩니다. 이렇게 흔적을 모아 개인을 가려내는 일을 핑거프린팅이라고 합니다.
넷째, 받는 쪽이 희망을 정확히 못 적는 경우가 많습니다. 브라우저가 보내는 언어 목록은 사용자가 직접 정한 것이 아니라 운영체제 설정에서 딸려 오기 쉽습니다. 그래서 고른 결과가 사람의 뜻과 어긋나는 일이 생기고, 그때는 화면 안에 언어를 바꾸는 버튼을 따로 두어 되돌려야 합니다.
다섯째, 맞는 표현이 하나도 없을 때의 처리가 애매합니다. 희망에 맞는 것이 없다고 거절하면 받는 쪽은 아무것도 못 받고, 아무거나 보내면 못 읽는 것을 받습니다. 어느 쪽을 고를지는 주는 쪽이 정합니다.
관련 항목
콘텐츠 협상이 잣대로 삼는 요청 헤더 필드
Accept · Accept-Language · Accept-Encoding · Accept-Charset · 품질값 · 헤더 · 필드
고른 결과를 알리는 응답 헤더 필드
Content-Type · Content-Language · Content-Encoding · Content-Location · Vary
협상의 대상이 되는 값과 형식
미디어 타입 · MIME · 문자 인코딩 · UTF-8 · gzip · Brotli · 언어 태그
콘텐츠 협상이 갈라 놓는 대상
자원 · 표현 · 선택된 표현 · 표현 메타데이터 · URI
협상 결과를 보관해야 하는 캐시 절차
캐시 · 캐싱 · 캐시 키 · 재검증 · 검증자 · 프록시 · CDN
이 방식이 실려 오는 프로토콜과 표준 문서
HTTP · RFC 9110 · 요청 메서드 · 상태 코드 · 300 Multiple Choices · 406 Not Acceptable
형식을 정하는 다른 수단
파일 확장자 · MIME 스니핑 · 쿼리 문자열 · 리다이렉트 · 사용자 에이전트 문자열
협상 정보가 낳는 위험
다른 이름: content negotiation · 콘텐트 협상 · 컨텐츠 협상 · 콘텐츠협상