HTTP 헤더와 콘텐츠 — 무엇을·어떤 형식으로
헤더는 왕복 서신의 곁들임 메모. 받는 곳의 Host, 형식과 문자 인코딩의 Content-Type, 양의 Content-Length, 희망을 전하는 Accept 계열, 그리고 스테이트리스 위에 세션을 만드는 Cookie 와 Set-Cookie 를 읽는다.
본문은 맞는데, 왠지 깨진다·다른 것이 돌아온다
HTML 은 올바르게 썼을 텐데 문자가 「譁�蟄�」처럼 깨진다. API 에 JSON 을 보냈는데 「형식이 잘못되었다」고 거부당한다. 같은 URL 인데 사람에 따라 다른 언어의 페이지가 나온다 — 이런 「본문은 맞는데」 계열의 트러블의 답은, 대개 본문이 아니라 헤더에 있습니다.
헤더는 왕복 서신에 곁들여지는 이름: 값 형식의 곁들임 메모입니다. 편지에 비유하면, 받는 이·「파손 주의」 라벨·무게 표기·회신용 이름표 — 본문 그 자체는 아니지만, 본문을 어떻게 다뤄야 하는지를 정하는 정보가 여기에 모여 있습니다. 3 점 세트의 세 번째 「조건과 보충」입니다.
헤더는 수백 종류가 있지만, 암기는 불필요합니다. 이 장에서는 「어디로 보내는가·무엇이 얼마나·어떤 희망으로·누구로부터인가」에 답하는 대표 6 가지만 읽습니다.
헤더는 본문에 곁들여진 라벨입니다. 받는 곳(Host)·내용물의 표시(Content-Type / Content-Length)·희망(Accept 계열)·이름표(Cookie / Set-Cookie) — 역할로 묶으면, 수백 종류의 헤더도 무섭지 않게 됩니다.
Host — 1 개의 주소에, 여러 개의 문패
리퀘스트의 필수 헤더 Host: example.com 은 편지의 받는 이입니다. 「IP 주소에 접속해 있으니 받는 곳은 자명하지 않은가?」라고 생각할지 모르지만, 실제로는 1 대의 서버(1 개의 IP 주소)에서 www.example.com 과 shop.example.com 처럼 여러 사이트를 함께 두는 운용이 보통입니다.
TCP 접속은 건물의 현관까지만 안내해 줍니다. 어느 방(사이트) 앞인지는 편지의 받는 이 — Host 헤더 — 로 전합니다. 서버는 이것을 보고 돌려줄 내용을 전환합니다. 이것이 이름 기반 가상 호스트이고, Host 가 빠진 리퀘스트가 HTTP/1.1 에서 오류가 되는 것은, 받는 이 없는 편지는 배달할 수 없기 때문입니다.
Content-Type 과 Content-Length — 내용물은 무엇이고, 얼마나 되는가
리스폰스의 Content-Type: text/html; charset=UTF-8 은 「내용물은 HTML 이고, 문자 인코딩은 UTF-8 입니다」라는 라벨입니다. 앞부분의 text/html(MIME 타입)로 브라우저는 「그려야 할 HTML 인가, 표시할 이미지인가, 다운로드시킬 파일인가」를 정하고, 뒷부분의 charset 으로 바이트열을 어느 문자 인코딩으로 문자로 되돌릴지를 정합니다.
여기가 글자 깨짐의 입구입니다. 실체는 UTF-8 로 저장되어 있는데 charset=Shift_JIS 라고 선언하면, 브라우저는 고지식하게 잘못된 규칙으로 바이트열을 읽어 문자가 깨집니다. 오류가 되지 않고 깨진 채로 표시되는 것이 성가신 점으로, 대처는 「라벨과 내용물을 일치시킨다」 이 한 가지입니다. 참고로 Content-Type 은 리스폰스 전용이 아닙니다 — POST 로 JSON 을 보낼 때는, 리퀘스트 쪽에 Content-Type: application/json 을 붙여 내용물의 형식을 전합니다.
Content-Length: 1256 은 「바디는 1256 바이트입니다」라는 양의 표기입니다. 1 개의 TCP 접속을 돌려쓰며 몇 통이든 편지를 주고받는 HTTP/1.1 에서는, 수신 쪽은 어디까지가 이번 바디인지를 알아야 하고, 이 숫자가 그 구분이 됩니다.
연습 4-1 — 받는 곳과 내용물의 곁들임 메모
Host 와 Content-Type, Content-Length — 「어디로 보내는가·무엇이·얼마나」를 읽습니다.
Q1. 1 대의 서버(1 개의 IP 주소)에서 www.example.com 과 shop.example.com 의 2 개 사이트를 운용하고 있습니다. 서버가 어느 사이트로 보낸 리퀘스트인지 판별하는 단서가 되는 헤더는 어느 것입니까.
TCP 접속은 IP 주소에 대해 맺어지므로, 접속만으로는 어느 사이트로 보낸 것인지 알 수 없습니다. 그래서 리퀘스트의 Host 헤더에 「www.example.com 앞입니다」라고 받는 곳을 적고, 서버는 그것을 보고 돌려줄 사이트를 전환합니다(이름 기반 가상 호스트). 1 개의 주소에 여러 문패가 걸린 건물에서, 편지의 받는 이를 보고 방으로 배달하는 구조입니다. Host 가 HTTP/1.1 에서 필수 헤더인 것은 이 때문입니다.
Q2. UTF-8 로 저장된 일본어 HTML 을 돌려주는 리스폰스에 Content-Type: text/html; charset=Shift_JIS 라는 헤더가 붙어 있었습니다. 무슨 일이 일어납니까.
브라우저는 원칙적으로 헤더의 선언을 믿고 바이트열을 해석합니다. 실체는 UTF-8 인데 Shift_JIS 라고 밝히면, 같은 바이트열이 다른 문자로 잘못 읽혀 글자가 깨집니다. 내용물과 라벨의 불일치 — 이것이 글자 깨짐의 입구의 전형이며, 고쳐야 할 것은 라벨(charset 선언)이나 내용물(저장 시의 문자 인코딩) 중 한쪽을 맞추는 것입니다. 오류가 되지 않고 깨진 채로 표시되어 버리는 것이 성가신 점입니다.
Q3. 리스폰스 헤더 Content-Length: 1256 의 의미로 올바른 것은 어느 것입니까.
Content-Length 는 바디의 바이트 수입니다. 1 개의 접속을 계속 돌려쓰는 HTTP/1.1 에서는 「어디까지가 이번 바디인가」를 수신 쪽이 알아야 하고, 이 숫자가 그 구분을 알려 줍니다. 행 수도 포트 번호도 아니며, 유효 기간은 다음 장에서 다루는 Cache-Control 의 일입니다. 개발자 도구의 Size 열의 값과 비교해 보는 장면에서도 등장합니다.
Accept 계열 — 희망을 전하고, 골라 받는다
같은 URL 이라도, 원하는 형태는 상대에 따라 다릅니다. 브라우저는 HTML 을 원하고, 프로그램은 JSON 을 원합니다. 일본어 화자에게는 일본어 페이지를 돌려주고 싶다. 이 「희망」을 전하는 것이 Accept 계열의 리퀘스트 헤더입니다.
서버는 제공할 수 있는 표현 중에서 골라 돌려줍니다. 이것이 콘텐츠 니고시에이션(내용의 교섭)입니다. 요점은 강제가 아니라 교섭이라는 것 — 희망이 통하지 않을 수도 있고, 실제로 무엇이 돌아왔는지는 리스폰스 쪽의 Content-Type 등으로 확인합니다. 「같은 URL 인데 사람에 따라 겉모습이 다르다」의 속사정 중 하나가 여기에 있습니다.
Cookie 와 Set-Cookie — 스테이트리스 위에 세션을 만든다
제 1 장의 숙제를 회수합니다. HTTP 는 스테이트리스라서, 서버는 「아까의 계속」을 기억하지 않는다. 그렇다면 로그인 상태는 어떻게 실현하고 있는가 — 답은 2 개의 헤더의 연계입니다.
Set-Cookie: session=abc123 을 곁들인다. 「이후에는 이 이름표를 보여 주세요」Cookie: session=abc123 이 붙는다. 서버는 이름표를 보고 「아까 그 사람이다」라고 안다즉 세션이란, 스테이트리스한 왕복 서신의 1 통 1 통에, 같은 이름표를 계속 곁들임으로써 만들어지는 「이어지는 이야기」입니다. 서버가 기억하고 있는 것은 「이름표 abc123 은 누구인가」라는 대응표뿐이고, HTTP 자체는 끝까지 스테이트리스인 채 — 이 구도를 읽을 수 있으면, 「Cookie 를 지웠더니 로그아웃되었다」「다른 브라우저에서는 로그인하지 않은 취급이 된다」 같은 일상의 현상이 전부 설명됩니다.
연습 4-2 — 희망을 전한다·이름표를 붙인다
Accept 계열의 콘텐츠 니고시에이션과, Cookie 에 의한 세션의 구조입니다.
Q4. 리퀘스트에 Accept: application/json 이나 Accept-Language: ja 를 붙이는 「콘텐츠 니고시에이션」의 설명으로 올바른 것은 어느 것입니까.
Accept 계열 헤더는 「가능하면 JSON 으로」「가능하면 일본어로」라는 희망의 표명이고, 서버는 준비할 수 있는 표현 중에서 골라 돌려줍니다(고른 결과는 리스폰스의 Content-Type 등으로 알 수 있습니다). 강제가 아니라 교섭이므로, 희망이 통하지 않을 수도 있습니다. 암호 방식의 합의는 TLS 의 핸드셰이크, 속도 조정은 TCP 의 일로, HTTP 헤더의 수비 범위가 아닙니다.
Q5. 로그인의 구조로 올바른 설명은 어느 것입니까.
HTTP 는 스테이트리스이므로(제 1 장), 서버는 접속을 「기억」하지 않습니다. 대신 서버가 Set-Cookie 로 이름표(세션 ID 등)를 건네고, 브라우저가 이후 리퀘스트의 Cookie 헤더에 매번 곁들임으로써, 서버는 「아까 그 사람이다」라고 알 수 있습니다. 비밀번호를 URL 에 싣는 방식은 기록이나 로그에 남는 악수이고, IP 주소는 공유·변동이 있어 식별자가 되지 못합니다.
Q6. Content-Type 헤더에 대해 올바른 설명은 어느 것입니까.
Content-Type 은 「이 메시지의 바디의 형식은 이것입니다」라는 곁들임 메모이므로, 바디를 가진 쪽이라면 양방향으로 붙습니다. POST/PUT 으로 폼이나 JSON 을 보낼 때는 리퀘스트 쪽의 Content-Type 이 부탁의 해석을 좌우하고, API 가 이것을 보고 415 등을 돌려주기도 합니다. HTML 전용이 아니며(JSON·이미지·PDF 무엇이든), text 계열에서는 charset 으로 문자 인코딩까지 지정합니다.
이 장에서 가져갈 것
- 헤더는 왕복 서신의 곁들임 메모. 본문을 어떻게 다룰지를 정하는 정보가 모인다
- Host 는 받는 이 — 1 개의 IP 에 여러 사이트가 함께 사는 이름 기반 가상 호스트를 지탱하는 필수 헤더
- Content-Type(+ charset)은 라벨. 라벨과 내용물의 불일치가 글자 깨짐의 입구. Content-Length 는 바디의 바이트 수로, 메시지의 구분을 알려 준다
- Accept 계열은 희망의 표명(교섭이지 강제가 아니다). Set-Cookie → Cookie 의 이름표 왕복이, 스테이트리스 위에 세션을 만든다
다음 장은 헤더의 응용편이자, Web 의 빠르기의 심장부 — 캐시입니다. 「같은 것을 두 번 나르지 않기」 위한 조건과 보충을, 손 계산과 시뮬레이터로 확인합니다.