내용 협상과 트랜스코딩

여러 나라에 대해서는 각 국의 언어로 콘텐츠를 적용해야 한다. 하지만 그렇게 된다면 하나의 URL이 여러 리소스에 대응할 필요가 있는데, HTTP에서는 클라이언트와 서버가 이러한 판단을 하기 위해 내용 협상(content-negotiation)을 한다. + 서로 다른 버전의 경우 variant 라고 한다.

내용 협상 기법

서버에 있는 페이지들 중에 어떤 것이 클라이언트에게 맞는지 판단하는 방법은 세 가지가 있다.

기법HOW?장점단점
클라이언트 주도클라이언트가 요청을 보내면 서버는 클라이언트에게 선택지를 보내주고 클라이언트가 선택구현하기 쉬움올바른 콘텐츠를 얻기 위해 두 번의 요청 대기 시간 증가
서버 주도서버가 클라이언트의 요청 헤더를 검증하여 제공할 콘텐츠 결정클라이언트 주도 협상보다 빠름
가장 적절한 것을 선택할 수 있도록 q값 메커니즘 제공 + 서버가 다운스트림 장치에게 요청이 어떻게 평가되는지 말해줄 수 있도록 하기 위해 Vary 헤더 제공
결정이 헤더에 맞는 것이 없으면 서버가 추측해야함
투명프락시 캐시 등 투명한 중간 장치가 협상웹 서버가 협상을 할 필요가 없음
클라이언트 주도 협상보다 빠름
정형화된 명세가 부재함

클라이언트 주도 협상

서버가 클라이언트의 요청을 받았을 때 가능한 페이지의 목록을 응답으로 돌려주어 클라이언트가 보고 싶은 것을 선택하는 방법이다.

  1. 클라이언트가 리소스를 요청
  2. 서버는 해당 리소스의 여러 표현(예: 언어, 포맷 등)을 나열하여 클라이언트에게 전달합니다.
  3. 클라이언트는 제공된 옵션 중에서 원하는 표현을 선택합니다.
  4. 클라이언트는 선택한 표현에 대한 리소스를 서버에 다시 요청

의 순서로 이루어진다.

이러한 방식은 각 페이지마다 두 번의 요청이 필요하기 때문에 클라이언트가 조금 더 신경 쓸 것들이 많아지며, 속도 또한 느려진다.

서버는 클라이언트에게 줄 선택지를 표현하는 두 가지 방법이 있다.

  • 버전과 설명에 대한 HTML 페이지 돌려주기
  • 300 Multiple Choices를 통해 선택지 넘겨주기
HTTP/1.1 300 Multiple Choices
Content-Type: application/json
 
{
  "options": [
    { "url": "/resource.en.html", "type": "text/html", "language": "en" },
    { "url": "/resource.fr.html", "type": "text/html", "language": "fr" }
  ]
}

증가된 대기 시간과 페이지당 여러번의 요청이 필요하다는 단점과 함께, 주 페이지와 특정 조건별 페이지를 요구하는 만큼 여러 개의 URL을 요구한다는 점도 단점으로 작용한다. 결국 API를 쏠 때 여기중에서 어디에 대해서 요청을 보내야 하는지도 문제가 된다.

서버 주도 협상

클라이언트와 서버의 커뮤니케이션을 증가시키는 단점을 보완하기 위한 서버 주도 협상은 서버가 어떤 페이지를 돌려줄 것인지 결정하게 한다. 하지만 여기서 클라이언트는 자신의 선호를 충분히 알려주고, 서버가 이를 판단할 수 있도록 해야 한다.

HTTP 서버가 클라이언트에게 보내줄 적절한 응답을 계산하기 위해 사용하는 메커니즘은 두 가지가 있다.

  • 내용 협상 헤더 살펴보기
    • Accept 관련 헤더에 맞는 응답 헤더 준비
  • 내용 협상 헤더 외 다른 헤더 살펴보기
    • 클라이언트의 User-Agent 헤더에 기반하여 응답

내용 협상 헤더

내용 협상 헤더는 HTTP 헤더 중 Accept 헤더와 관련해서 선호 정보를 보낼 수 있다.

  • Accept : 서버가 어떤 미디어 타입으로 보내도 되는지
  • Accept-Langauge: r서버가 어떤 언어로 보내도 되는지
  • Accept-Charset : 서버가 어떤 차셋으로 보내도 되는지
  • Accept-Encoding : 서버가 어떤 인코딩으로 보내도 되는지 등을 헤더를 통해 알려준다.

엔터티 헤더와는 비슷하지만, 엔터티 헤더는 메시지를 서버에서 클라이언트로 전송할 때 필요한 메시지 본문 속성을 가리키는 반면, 내용 협상 헤더의 경우 클라이언트와 서버가 선호 정보를 서로 교환하고 문서들의 여러 버전 중 하나를 선택하는것을 도와 가장 잘 맞는 문서를 제공해주기 위함이라는 점에서 둘의 차이점이 나타난다.

엔터티 헤더들과 Accept 관련 헤더들은 서로 짝이 지어져있다.

Accept 헤더엔터티 헤더
AcceptContent-Type
Accept-LanguageContent-Langauge
Accept-CharsetContent-Type
Accept-EncodingContent-Encoding

클라이언트가 각 자신의 상황에 따라 맞는 언어 등의 설정을 Accept 헤더에 담아 보내면 서버는 이를 기반으로 판단할 수 있으므로 커뮤니케이션 대기 시간을 줄여준다.

하지만 만약 언어를 생각했을 때, 모든 언어를 지원하지 않을 수도 있다. 그럴 경우에는 어떻게 해야 할까? 이 경우에는 클라이언트 주도 모델을 통해 클라이언트에게 선택권을 주고, 이에 대한 정보를 선호 정보에 추가하여 전달할 수 있도록 HTTP의 품질값을 통해 값을 전달할 수 있는 메커니즘을 제공한다.

내용 협상 헤더의 품질값

클라이언트가 각 선호의 카테고리마다 여러 선택 가능한 항목을 선호도와 함께 나열할 수 있도록 품질값(q값)을 정의하는데,

Accept-Langauge: en;q=0.5, fr;q=0.0; nl;q=0.0, tr;q=0.0

과 같은 형식으로 보낸다.

여기서 0.0부터 1.0까지 선택할 수 있으며 이 선호도는 곧 우선순위를 의미한다.

그 외의 헤더들에 의해 결정

서버는 User-Agent와 같은 클라이언트의 다른 요청 헤더들을 이용해 알맞은 요청을 만들어 내려고도 한다.

User-Agent 서버와 네트워크 피어가 요청하는 애플리케이션, 운영체제, 제조업체, 사용자 에이전트 요청 버전을 식별하는 문자열

이 경우에는 q값 메커니즘이 없어 최선의 버전 제공이 불가능하기 때문에, 서버가 이를 잘 탐지하여 정확한 대응을 찾아내거나 갖고 있는 것을 제공해줘야 한다.

캐시는 반드시 캐시된 문서의 올바르고 최선의 버전을 제공해주려 하기 때문에 HTTP 프로토콜에 Vary 헤더를 정의한다.

Vary 헤더 요청 메시지의 메서드 및 URL을 제외하고 응답 내용에 영향을 준 부분을 설명 응답 생성에 영향을 줄 수 있는 요청 헤더들을 쉼표로 구분한다

웹서버의 내용 협상

내용 협상은 웹 사이트 콘텐츠 제공자에게 달려있다. 색인 페이지에 대한 여러 버전을 제공해주기 위해선 각각의 버전에 해당하는 파일을 모두 적절한 디렉토리에 넣어줘야 한다.

Nginx의 경우에는

map $http_accept_language $lang {
    default en;
    ~*fr fr;
    ~*ja ja;
}
 
server {
    listen 80;
    server_name example.com;
 
    location / {
        try_files /index.$lang.html /index.en.html;
    }
}
 

이런 식으로 파일 기반으로 Accept-Language 헤더를 보고 이에 해당하는 언어의 파일을 탐색하여 보내주는 방식이 있다.

아파치의 경우에는 type-map 파일을 사용하는 방식이 있다. variant를 갖는 웹사이트의 각 URIㅇ를 위한 type-map 파일을 만들고 이에 따른 내용 협상 헤더를 나열한다.

URI: index.html.en
Content-type: text/html
Content-language: en
 
URI: index.html.fr
Content-type: text/html
Content-language: fr
 
URI: index.html.de
Content-type: text/html
Content-language: de
 

다른 방법으로는 그 디렉터리에 대해 자동으로 type-map파일을 생성하도록 하는 MultiViews 지시어 Options를 켬으로써 적절한 내용 협상 헤더를 추측하게 하는 방법도 있다. 이 경우 파일의 확장자에 index.html.en, index.html.fr 과 같이 각 언어별 버전을 명시한다.

둘의 주요한 차이는 Apache에서는 웬만해서 기본적으로 기능들을 지원하는데 반해, Nginx는 서드파티 모듈을 통해 설정하는 경우가 많다는 점이다.

기능ApacheNginx
기본 콘텐츠 협상 지원O (mod_negotiation)X (추가 구성 필요)
MultiViews 지원OX
언어 우선순위 설정O (LanguagePriority, ForceLanguagePriority)X (커스텀 구현 필요)
투명한 협상 지원O (RFC 2295, 2296)X
서드파티 모듈 지원제한적O (ngx_http_accept_language_module 등)
Lua 스크립트 활용제한적O (ngx_http_lua_module 사용 가능)

투명 협상

투명 협상은 클라이언트 입장에서 협상하는 중개자 프락시를 둠으로써 클라이언트와의 메시지 교환을 최소화하는 동시에 서버 주도 협상으로 인한 부하를 서버에서 제거한다.

투명한 내용 협상을 위해 어떤 요청 헤더를 검사해야 하는지 프락시에게 반드시 말해줄 수 있어야 하고, 이는 Vary 헤더를 통해 내용 협상이 이루어진다.

캐시 프락시의 경우 단일한 URL을 통해 접근할 수 있는 문서의 여러 다른 사본을 저장할 수 있다. 서버가 캐시에 대한 의사결정 프로세스를 캐시에게 알려준다면 캐시는 서버의 입장에서 클라이언트와 소통하고 협상할 수 있는 것이다.

캐시는 추가적으로 콘텐츠를 트랜스코딩하기 좋은 곳인데, 캐시 안 범용 트랜스코더는 특정 서버에 국한되지 않고 어떤 서버의 콘텐츠든 트랜스코딩할 수 있기 때문이다.

캐시와 얼터네이트

캐시는 클라이언트에게 올바로 캐시된 응답을 돌려주기 위해 응답을 돌려주기 위해 사용했던 의사결정 로직의 상당 부분을 재사용한다. 즉, 캐시는 캐시된 응답을 보낼 때 같은 헤더를 사용해야 한다는 의미이다.

위 사진처럼 캐시는 첫번째 요청에 대해 서버에 그대로 저장을 하지만, 두 번째 요청에는 요청한 언어가 다르기 때문에 두번째 요청 또한 그대로 저장해야 한다. 이 경우, 웹서버에는 하나의 URL에 대해서 두 개의 다른 문서를 갖게 된다. 이러한 다른 버전의 문서를 variantalternate 라고 부른다. 내용 협상은 이 배리언트(variant) 중에서 클라이언트의 요청에 가장 잘 맞는 것을 선택하는 과정이다.

Vary 헤더

서버가 어떤 페이지를 반환할 것인지 판단하기 위해 다른 헤더들을 사용하고 있다면, 캐시는 반드시 그 헤더들이 무엇인지 알아야 하고 캐시된 페이지 중 어떤 것을 반환할지 선택할 때 서버가 했던 것과 같은 논리를 적용해야 한다. HTTP Vary 헤더는 위에서 말한 것처럼 페이지를 반환하기 위해 사용한 헤더들을 나열한 헤더이다.

새 요청에 대해서 캐시가 문서를 클라이언트에게 제공해 줄 수 있게 되기 전에 캐시는 반드시 캐시된 응답 안에 서버가 보낸 Vary 헤더가 들어있는지 확인하고, 만약 있다면 Vary 헤더가 명시하고 있는 헤더들은 새 요청과 캐시된 요청에서의 값이 같아야 한다.

캐시는 각 배리언트마다 알맞은 문서 버전을 저장하고, 캐시가 검색을 할 때 내용 협상 헤더로 적합한 콘텐츠를 찾은 후 캐시된 배리언트와 요청의 배리언트를 비교하여 맞는게 없으면 서버에서 문서를 가져오게 된다.

트랜스코딩

서버가 클라이언트의 요구에 맞는 문서가 아예 없다면 에러로 응답하는 것이 맞지만 이론적으로 서버는 기존의 문서를 클라이언트가 사용할 수 있는 무언가로 변환할 수 있는데, 이를 트랜스코딩 이라고 한다.

이러한 트랜스코딩에는 포맷 변환, 정보 합성, 내용 주입 세 종류가 있다.

포맷 변환

포맷 변환은 데이터를 클라이언트가 볼 수 있도록 한 포맷에서 다른 포맷으로 변환하는 것이다. 기존의 HTML을 WML으로 변환한다던지, 이미지 변환/축소하는 등의 포맷 변환은 내용 협상 헤더에 의해 주도된다. 인코딩이랑은 다른 개념으로, 특정 접근 장치에서 볼 수 있도록 하기 위한 목적이라 효율적인 또는 안전한 전송을 위한 목적의 인코딩과 다르다.

정보 합성

문서에서 정보의 요점을 추출하는 것이다. 페이지 광고 제거 등의 기능이다. 본문의 키워드에 기반하여 페이지를 분류함으로써 문서의 핵심을 요약할 때 유용하며 자동화된 웹페이지 분류 시스템 등에 사용된다.

콘텐츠 주입

포맷 변환과 정보 합성은 웹 문서의 양을 줄이지만, 콘텐츠 주입의 경우 양을 늘린다. 광고 생성이나 사용자 추적 등이 있다.

트랜스코딩 vs 정적으로 미리 생성해놓기

트랜스코딩의 대안은 웹 서버에서 미리 여러 사본을 만드는 방식이다. 하지만 이렇게 미리 만들어놓는 경우, 작은 변동사항이 있을 때 모든 페이지를 수정해놔야 하고, 저장공간 차지, 요청에 따른 페이지 제공 로직 복잡성 증가 등의 단점을 가지게 된다. 특히 광고 삽입과 같은 것들은 사용자 활동 기반으로 생성되므로 동적으로밖에 되지 않는다.

루트페이지를 필요할 때마다 변환하는 것은 정적으로 생성하는 것보다 쉽겠지만 레이턴시가 증가할 수밖에 없다. 하지만 이런 부분은 더 싼 프락시나 캐시에 있는 외부 에이전트를 통해 수행하면서 줄일 수 있다.

다음 단계

내용 협상은 Accept나 Content 헤더로 끝나는 이야기가 아니다. HTTP 내용 협상의 경우 결국 여러 배리언트를 탐색하면서 가장 잘맞는 것을 추측해야 하는 비용이 발생하고 이는 곧 성능 제약으로 이어진다.

또한 HTTP가 내용 협상이 필요한 유일한 프로토콜이 아니다. SMTP,IMAP과 같은 프로토콜도 역시 내용 협상 개념이 적용된다. 일반적인 내용 협상 프로토콜이 TCP/IP 응용 프로토콜 위에서 만들어질 수 있을까에 대해 콘텐츠 협상 작업 그룹이 생기기도 하였다.