HTTP 스펙에는 HTTP 메시지 형식에 대한 정의가 포함되어 있다. HTTP 메시지에는 요청 메시지와 응답 메시지의 두 종류가 있다.
GET /somedir/page.html HTTP/1.1
Host: www.someschool.edu
Connection: close
User-agent: Mozilla/5.0
Accept-language: fr
위의 전형적인 HTTP 요청 메시지를 통해 다음과 같은 사실들을 알 수 있다.
1. 메시지는 아스키 텍스트로 쓰여 있다.
2. 각 라인의 뒤에는 캐리지 리턴과 라인 피드가 있다(줄바꿈으로 라인을 구분한다).
3. 마지막 라인에도 추가적으로 캐리지 리턴과 라인 피드가 있다(줄바꿈으로 마지막 라인임을 표시한다).
캐리지 리턴 :
\r
. 커서를 해당 줄의 맨 앞으로 이동
라인 피드 :\n
. 커서를 다음 줄로 이동HTTP 명세에서는
\r\n
을 줄 바꿈으로 사용하지만, 견고한 애플리케이션의 경우\n
만으로도 동일하게 동작할 수 있어야 한다고 함.
위 요청 메시지의 경우 라인이 다섯 개 밖에 없지만, 라인이 더 많을 수도, 적을 수도 있다.
HTTP 요청 메시지의 첫 번째 라인을 요청 라인(request line)이라고 하고, 그 이후의 라인들을 헤더 라인(header line)이라고 한다.
요청 라인에는 메서드, URL, HTTP 버전의 세 필드가 있다.
GET
, POST
, HEAD
, PUT
, DELETE
등이 들어간다.Host: www.someschool.edu
헤더 라인은 객체를 가지고 있는 호스트를 명시한다.Connection: close
헤더 라인은 브라우저가 지속 연결을 하지 않겠다는 말이다. User-agent:
헤더는 서버에 요청을 보내는 브라우저의 종류다. Accept-language:
헤더는 만약 서버에 객체의 프랑스어 버전이 있다면, 사용자가 해당 버전을 원함을 나타낸다.요청 메시지는 일반적으로 다음과 같은 형식을 가진다.
여기서 볼 수 있는 것은, 위 예시와 달리 헤더 라인들 뒤에 엔티티 바디(Entity Body)가 하나 더 추가됐다는 점이다. GET
메서드의 경우 엔티티 바디가 비어있지만, POST
메서드를 사용하는 경우 엔티티 바디에 내용을 채워 보낸다.
POST
의 경우 웹 페이지의 폼(form)을 채워 해당 내용에 맞게 웹 페이지 컨텐츠를 요청하는 데 쓰이며, 이때 사용자가 폼에 입력하는 정보는 이 엔티티 바디에 들어가가게 된다.POST
메서드를 사용할 필요는 없다. GET
을 쓰면서 URL에 해당 정보들을 명시할 수도 있다. HEAD
메서드의 경우 GET
과 비슷하다. 서버는 HEAD
메시지를 받으면 요청된 객체를 제외한 HTTP 메시지로 응답을 한다. 디버깅을 위해 헤더만을 이용하면 되는 경우, 굳이 큰 객체를 받지 않아도 되므로 HEAD
메서드를 사용한다.PUT
메서드는 사용자나 애플리케이션이 웹 서버의 특정 경로에 객체를 업로드하려 할 때 쓰인다. DELETE
는 사용자나 애플리케이션이 웹 서버에 있는 객체를 삭제하는 요청을 보낼 때 쓰인다.아래는 전형적인 HTTP 응답 메시지다.
HTTP/1.1 200 OK
Connection: close
Date: Tue, 18 Aug 2015 15:44:04 GMT
Server: Apache/2.2.3 (CentOS)
Last-Modified: Tue, 18 Aug 2015 15:11:03 GMT
Content-Length: 6821
Content-Type: text/html
(data data data data data ...)
이 메시지는 상태 라인(status line), 헤더 라인들, 엔티티 바디의 세 섹션으로 나뉘어져 있다.
Connection: close
서버는 메시지를 보내고 나서 TCP 연결을 끊을 것임을 클라이언트에게 알리고 있다.Date:
서버가 HTTP 응답을 만들고 전송한 시각을 말한다.Server:
헤더는 이 메시지가 어떤 웹 서버에서 만들어진 것인지를 나타낸다User-agent:
헤더와 비슷하다.Last-Modified:
는 로컬 클라이언트와 네트워크 캐시 서버(프록시 서버)에서의 객체 캐싱에 쓰인다(후술).Content-Length:
는 서버가 보낸 객체가 몇 바이트인지를 나타낸다.Content-Type:
은 엔티티 바디 내의 객체가 HTML 텍스트임을 나타낸다.엔티티 바디에는 요청된 객체 그 자체가 들어갈 수 있다.
상태 코드와 상태 메시지는 요청의 결과가 어떤 상태인지를 말한다. 흔히 쓰이는 상태 코드 및 메시지에는 다음과 같은 것들이 있다.
200 OK
: 요청이 성공했고, 요청한 정보도 응답에 잘 들어가 반환됐다.301 Moved Permanently
: 요청한 객체가 응답 메시지의 Location:
헤더에 담긴 URL로 영구적으로 이동했다. 이 경우 클라이언트는 보통 자동적으로 해당 URL을 검색한다.400 Bad Request
: 일반적으로, 포괄적인 의미르 쓰이는 에러 코드로, 서버가 해당 요청을 처리할 수 없음을 말한다.404 Not Found
: 요청한 문서가 서버에 존재하지 않는다.505 HTTP Version Not Supported
: 서버가 요청한 HTTP 프로토콜 버전을 지원하지 않음을 나타낸다.응답/요청 헤더에는 위에서 말한 것들 외에도 여러 종류들이 있다. 그렇다면 브라우저는 요청 메시지에 어떤 헤더들이 들어가야 하는지 어떻게 알 수 있을까? 또 웹 서버는 응답 메시지에 어떤 헤더들이 들어가야 하는지 어떻게 알 수 있을까? 브라우저는 브라우저의 종류와 버전, 브라우저의 사용자 설정, 그리고 그 외의 정보들의 함수로 헤더 라인을 생성한다. 웹 서버도 비슷하다.
앞서 HTTP 서버는 무상태적이라 했다. 서버를 무상태로 유지하면 서버 설계도 단순화할 수 있고, 동시에 수 천의 TCP 연결을 처리하는 고성능 웹 서버 개발도 용이해진다. 하지만 때로는 웹 사이트에서 사용자를 식별해야 할 때가 있을 수도 있다. 이를 위해서 HTTP는 쿠키를 사용한다. 웹 사이트는 쿠키를 이용해 사용자를 추적한다.
쿠키 기술은 네 요소로 이루어진다.
서버는 추적이 필요한 정보를 응답 메시지의 Set-cookie:
에 담아 보낸다.
Set-cookie: 1678
브라우저는 응답을 받으면 Set-cookie:
헤더를 보고, 자신이 관리하는 특별한 쿠키 파일에 해당 라인을 추가한다. 이 라인에는 서버의 호스트명과 Set-cookie:
헤더의 내용이 들어 있다.
사용자는 이후 사이트를 이용할 때, 필요하다면 요청에 쿠키의 내용을 함께 담아 보낸다.
Cookie: 1678
서버는 요청에 함께 담겨온 쿠키의 정보를 통해 사용자의 정보를 추적한다.
사용자는 처음 사이트를 방문할 때 서버에 자신의 신원을 제공하고 쿠키를 받아 온다. 이후의 세션들에서 브라우저는 서버에 쿠키 헤더를 전달해, 서버가 이를 가지고서 사용자를 식별할 수 있도록 한다.
쿠키는 사용자 경험도 증진시킬 수 있고, 사용하기도 편하지만 개인 정보 침해의 문제가 있기는 하다.
웹 캐시(Web cache), 혹은 프록시 서버(proxy server)는 원래 웹 서버를 대신해 HTTP 요청을 처리해주는 네트워크 엔티티다. 웹 캐시는 자신의 디스크 저장소에 최근에 요청된 객체의 복사본을 저장해놓는다.
사용자의 브라우저는 일단은 사용자 HTTP 요청을 웹 캐시로 다이렉트 되도록 설정될 수 있다. 이때,
1. 브라우저는 웹 캐시와 TCP 연결을 맺고, 웹 캐시에 객체에 대한 HTTP 요청을 보낸다.
2. 웹 캐시는 자신이 해당 객체에 대한 복사본을 가지고 있는지 확인한다. 만약 그렇다면 웹 캐시는 해당 객체를 HTTP 응답 메시지에 담아 클라이언트 브라우저에 보낸다.
3. 만약 객체를 가지고 있지 않으면, 웹 캐시는 원래 서버와의 TCP 연결을 열고, 서버에 해당 객체에 대한 요청을 보낸다. 원 서버는 이 요청을 받으면 객체를 HTTP 응답에 담아 웹 캐시로 보낸다.
4. 웹 캐시는 객체를 받으면 그 복사본을 자신의 저장소에 저장해놓고 복사본을 HTTP 응답 메시지에 담아 클라이언트 브라우저로 보낸다.
캐시는 사용자 브라우저에게는 서버로, 원래 서버에게는 클라이언트로 동작한다. 캐시를 이용함으로써 얻을 수 있는 이점에는 다음의 두 가지가 있다.
예컨대 위와 같은 경우, 아래와 같이 LAN에 캐시를 추가한다면 퍼블릭 인터넷으로의 트래픽을 발생시키지 않으면서, 훨씬 더 빠른 속도로 컨텐츠를 받아올 수 있게 된다.
문제는 캐시에 들어 있는 복사본이 너무 오래된 것일 수도 있다는 점이다. 원래 서버에 있는 해당 컨텐츠는 캐시에 복사된 이후 수정되었을 수도 있고, 따라서 캐시를 이용하는 경우 사용자는 해당 컨텐츠의 최신본이 아니라 오래된 복사본만을 받게 될 수도 있다.
다행히도 HTTP에는 객체가 최신본임을 증명하는 메커니즘이 있다. 이 메커니즘을 가리켜 조건부 GET(conditional GET)이라 부른다. 조건부 GET 메시지는 (1) GET
메서드를 사용하면서 (2) If-Modified-Since:
헤더 라인이 있는 HTTP 요청 메시지를 말한다.
웹 캐시가 브라우저 대신 원래 서버에 다음과 같은 요청 메시지를 보냈다고 하자.
GET /fruit/kiwi.gif HTTP/1.1
Host: www.exotiquecuisine.com
웹 서버는 요청된 객체를 응답 메시지에 담아 웹 캐시에 보낸다.
HTTP/1.1 200 OK
Date: Sat, 3 Oct 2015 15:39:29
Server: Apache/1.3.0 (Unix)
Last-Modified: Wed, 9 Sep 2015 09:23:24
Content-Type: image/gif
(data data data data data ...)
캐시는 받은 객체를 요청 브라우저에 보냄과 동시에 해당 객체를 자신의 저장소에도 캐싱한다. 시간이 지나고 다른 브라우저가 동일한 객체에 대한 요청을 보냈고, 해당 객체가 여전히 캐시에도 남아 있다고 해보자. 그 시간 동안 웹 서버에 있는 객체에 수정이 발생했을 수도 있기 때문에, 캐시는 조건부 GET을 통해 해당 시점 이후로 객체에 수정 사항이 있는지를 물어보고, 수정이 있는 경우에만 응답에 객체를 담아 보내달라는 요청을 보낸다.
GET /fruit/kiwi.gif HTTP/1.1
Host: www.exotiquecuisine.com
If-modified-since: Wed, 9 Sep 2015 09:23:24
여기서 If-modified-since:
헤더는 이전에 웹 서버로부터 받은 Last-Modified:
응답 헤더의 내용과 동일하다.
수정 사항이 없었다고 해보자. 수정이 없는 경우, 웹 서버는 객체를 따로 보내지 않고 다음과 같은 응답 메시지만 캐시에 보낸다. 굳이 변한 게 없는데 큰 객체를 보내는 것은 낭비기 때문이다.
HTTP/1.1 304 Not Modified
Date: Sat, 10 Oct 2015 15:39:29
Server: Apache/1.3.0 (Unix)
(empty entity body)
상태 라인의 304 Not Modified
를 확인한 웹 캐시는 자신이 가지고 있는 복사본을 클라이언트 브라우저에 제공한다.