HTTP 1.1과 그 주변 스펙으로 정의되어 있는 헤더를 값의 종류와 용도에 대응하여 알아보자, 또한 헤더로 구현할 수 있는 HTTP 기능에 대해서도 알아봅시다.
헤더는 메세지의 바디에 대한 부가적 정보, 즉 메타 데이터를 표현한다. 클라이언트와 서버는 헤더를 보고 메세지에 대한 동작을 결정한다. 미디어 타입과 언어 태그 등 프레임워크가 아닌 구현하는 사람이 구체적으로 설정해야만 하는 헤더도 많다.
또한, 리소스에 대한 접근권한을 설정하는 인증이나 캐시 같은 HTTP 기능을 헤더로 실현한다. 인증이나 캐시 등의 기능은 헤더를 메서드와 스테이터스 코드와 조합해야 비로소 구현할 수 있게 된다.
때문에 HTTP 헤더도 스테이터스 코드나 메서드와 같이 HTTP의 중요한 요소 중 하나이다.
0.9 버전에서는 헤더가 없었다. HTTP의 스펙 책정이 진행됨에 따라 HTTP로 전송하는 문서의 메타 데이터를 표현하기 위해 전자메일의 메세지 스펙의 헤더형식을 빌려오는 식으로 추가되었다.
이 때문에 HTTP 헤더에는 전자메일의 메세지 헤더와 공통되는 부분이 있다.
때문에 HTTP 헤더의 상세한 내용을 배우기 위해서는, 전자메일 메시지의 스펙을 알아야 할 필요가 있다. 메일과 HTTP가 인터넷의 성장과 함께 규정되어 왔던 역사를 생각해본다면 어쩔 수 없다.
RFC 822 메세지는 심플한 헤더, 바디 형식의 포멧으로 장점도 많지만, 역사적 경위에서 온 제약이나 부적절한 사용예도 존재한다.
그 대부분은 헤더에 7bit ASCII 코드 이외의 문자를 넣을 수 없다는 것에 기인한다. HTTP 헤더에도 역시 문자 인코딩의 제한이 있다. 라틴 알파벳을 위한 문자 인코딩인 ISO8859-1 이외의 문자가 들어갈 수 없다.
그러므로 메일과 마찬가지로 부적절한 사용예가 HTTP에도 존재한다.
전자메일 프로토콜과 HTTP에는 차이점도 있다. 가장 큰 차이는 메세지를 주고 받지 않는다는 것, HTTP는 한 번의 통신으로 요청/응답 두가지 메세지를 주고 받는다. 때문에 전자메일에는 없는 다양한 헤더를 추가한다.
이제부터 HTTP 헤더를 순서대로 살펴보자.
우선 값으로 날짜와 시간을 가지는 헤더이다. Date와 Expires가 이에 해당한다. 메일의 헤더는 타임 존이 붙는 서식을 허가하지만, HTTP에서는 GMT로 기술하도록 되어있다.
메세지를 주고받는 리소스 표현의 종류를 지정한다. Multipurpose internet Mail Extensions의 약자이며 이 또한 전자메일에서 가져온 스펙이다.
###Content-Type - 미디어 타입을 지정한다.
Content-Type 헤더는 그 메세지의 바디 내용이 어떠한 종류인가를 미디어 타입으로 나타낸다. /를 기준으로 왼편을 타입, 오른편을 서브타입이라고 부른다.
Content-Type: application/xhtml+xml; charset=utf-8
타입의 종류는 임의로 늘릴 수 없고 정해진 규약이다. 서브타입은 비교적 자유롭게 늘릴 수 있다. IANA의 웹 사이트에서 소정의 형식으로 등록할 수 있고 접두어를 붙여서 독자적인 서브타임을 만들 수도 있다.
미디어 타입은 charset 파라미터를 가질 수 있다. 위의 예시는 UTR-8로 인코딩 한다는 것을 나타낸다. charset 파라미터는 생략가능하나 타입이 text인 경우에는 주의가 필요하다.
HTTP 에서 text 타입의 디폴트 문자 인코딩은 ISO 8859-1이라고 정의하고 있다. 때문에 한글이 들어가면 문자가 깨질 가능성이 있다.
더욱 까다로운 것은 XML처럼 문서 자체에서 문자 인코딩 방식을 선언할 수 있는 경우라도 text 타입의 경우는 Content-Type 헤더의 charset 파라미터를 우선한다. 때문에 text 타입인 경우에는 charset 파라미터를 꼭 붙이자.
charset 파라미터는 문자 인코딩 방식이라면, 리소스 표현의 자연언어를 지정하는 헤더도 존재한다. 그것이 Content-Language 헤더이다.
Content-Language 헤더의 값은 '언어 태그'라고 불리는 문자열이다.
Content-Language: ko-KR
언어 태그의 '-'의 왼편에는 ISO 639가 정의하는 언어코드가 들어간다. -의 오른편에는 ISO 3166이 정의하는 지역코드가 들어간다.
지금까지 설명했던 미디어타입과, 문자 인코딩, 언어 태그는 서버가 일방적으로 결정하는 것 뿐 아니라 클라이언트와 교섭해서 정할 수 있다. 이 방법을 콘텐트 네고시에이션이라고 한다.
클라이언트가 자신이 처리할 수 있는 미디어 타입을 서버에게 전달할 경우는 Accept 헤더를 이용한다.
q= 이라는 파라미터 값을 qvalue라고 하며, 그 미디어 타입의 우선순위를 나타낸다.
qvalue는 소수점 이하 세 자리 이내의 0~1까지의 수치이며 수치가 클 수록 우선된다. 이 예의 경우, text/html, application/xhtml + xml이 디폴트인 1, application/xml이 0.9 그밖의 모든 미디어 타입이 0.8이라는 우선도를 가진다.
클라이언트가 Accept 헤더에 지젖ㅇ한 미디어 타입에 서버가 대응하고 있지 않다면, 406 Not Acceptable이 반환된다.
다음으로 봐야 하는 것은 클라이언트가 XML형식 또는 Word 형식의 표현을 지정하고 서버는 그에 대응할 수 없는 경우의 예이다.
클라이언트가 자신이 처리할 수 있는 문자 인코딩을 서버에 전달 할 때 사용한다.
클라이언트는 처리할 수 있는 언어 태그를 서버에게 전달하기 위해서 Accept-Language 헤더를 이용한다.
한국어가 기본값 1, 미국영어가 0.7, 지역을 특정하지 않은 영어가 0.3이라는 우선도를 가진다.
메세지가 바디를 가지고 있다면 기본적으로 Content-Length 헤더를 이용하여 사이즈를 10진수의 바이트로 나타낸다.
하지만, 동적으로 이미지를 생성하는 웹 서비스의 경우, 파일 사이즈가 정해질 때까지 응답할 수 없기 때문에 응답 성능이 저하되고 만다.
이때는 Transfer-Encoding
를 사용한다.
현재 주류인 HTTP 인증 방식에는 HTTP 1.1이 규정하고 있는 Basic 인증과 Digest 인증이 있다. 웹 API에서는 WSSE라는 HTTP 인증의 확장 스펙을 이용하는 경우도 있다.
어떤 리소스에 엑세스 제어가 걸려 있다면, 스테이터스 코드 401 Unauthorized와 WWW-Authenticate 헤더를 이용하여 클라이언트에 리소스 접근에 필요한 인증정보를 통지할 수 있다.
Basic 인증은 유저 이름과 패스워드에 의한 인증 방식이다. 유저 이름과 패스워드는 Authorization 헤더에 넣어 요청마다 전송한다.
Authorization 헤더의 내용은 인증 방식에 있어서 유저 이름과 패스워드를 :로 연결하고 Base64 인코딩으로 한 문자열이 된다.
주의해야 할 것은 간단히 디코딩이 가능하는 점이다. Basic 인증을 사용하려면 SSL, TLS 등을 사용하여 HTTPS 통신을 하고 통신선로 상에서 암호화 할 것 인지 검토해야만 한다.
Basic 인증 보다 보안이 강화된 인증 방식이다. 메세지에 대해 해시함수를 적용한다. 조금 복잡한 흐름으로 진행이 되므로 알아보자
우선 인증 정보 없이 요청을 송신한다. 그 결과로 인증이 실패하고 401 unauthorized가 돌아온다.
WWW-Authenticate 헤더의 값이 복잡해졌는데, 이를 챌린지라고 부른다. 클라이언트는 챌린지를 사용하여 다음 요청을 조립한다.
'nonce'는 number used once 의 줄임말로, 모든 요청에 대해 변화하는 문자열이다. nonce의 값은 서버 구현에 의존하는데, 기본적으로는 타임스탬프와 서버만 알 수 있는 패스워드를 이용하여 생성한다.
타임스탬프가 포함되어 있는 이유는 이 nonce를 사용한 요청의 유효기간을 좁히기 위함이다. nonce는 생성할 해시 값의 보안을 좀 더 강화할 목적으로 이용한다.
'qop'는 quality of protection'의 줄임말로 auth나 auth-init을 지정한다. qop 값은 클라이언트가 송신할 다이제스트의 작성방법에 영향을 미치고 auth의 경우에는 메서드와 URI로부터 다이제스트를 작성하는데 반해 auth-init의 경우엔 메서드와 URI에 추가해 메세지 바디도 이용한다. 다시말해 POST와 PUT으로 바디를 송신 할 때 auth-init을 사용하면 메세지 전체가 변경되지 않음을 보증할 수 있다.
'opaque'는 그 이름 그대로 클라이언트에는 불투명한 문자열로, 동일 URI 공간에 대한 요청에는 공통되게 클라이언트에서 서버로 보낸다.
서버로부터 인증에 필요한 정보를 얻은 클라이언트는 자신의 유저 이름과 패스워드를 사용하여 다이제스트를 생성한다.
클라이언트는 생성한 다이제스트 값을 response라는 필드에 넣고 다음과 같이 응답을 전송한다.
인증을 통과하고 삭제가 성공하면 200이 반환된다.
Basic 인증과는 달리 Digest 인증에서는 패스워드를 도둑맞을 위험성이 없다. Digest 인증에서는 서버에 패스워드의 해시 값만 보관해두면 되기 때문에 패스워드 자체를 서버에 맡겨두지 않아도 된다.
다만 패스워드만 암호화할 뿐이기 때문에 메세지 자체는 평문으로 네트워크로 흘러간다. 메세지를 암호화하고 싶은 경우는 Basic 인증의 경우와 마찬가지로 HTTPS를 이용한다.
클라이언트 쪽에서 보면 조작이 번잡하다. 클라가 다이제스트를 계산할 수 없는 경우(nonce 가 없을 때). 때문에 그다지 보급되지 못했다.
또한 Apache 등의 웹 서버에서는 Digest 인증이 옵션으로 되어있다. 호스팅 서비스에서 지원하지 않을 가능성도 있다.
그럴 때는 직접 인증 프로그램을 만들면 되지만 CGI처럼 별도의 프로세스로 동작하는 프로그램을 만들면 되지만 CGI처럼 별도의 프로세스로 동작하는 프로그램에는 보안 문제로 인해 Apache가 인증관련 헤더를 건네주지 않아도 되기 때문에 Digest 인증을 이용할 수 없다.
HTTP 1.1 표준 외의 인증 방식이다. AtomPub 같은 웹 API의 인증에 사용된다. SSL과 TLS의 이용이 불가능해 Basic 인증을 사용하지 못하며, 호스팅 서비스 상의 CGI 스크립트 등으로 Digest 인증도 사용할 수 없는 경우 사용한다. 여기서 WSSE는 'WS-Security Extension'의 약자이다. WSSE 인증에서도 클라이언트는 우선 인증 정보 등으로 요청을 보내고, 서버로부터 401 Unauthrozied 응답을 받는다.
profile의 값은 현 시점에서는 UsernameToken만 준비되어 있고, 클라이언트는 패스워드와 자신이 준비한 nonce와 일시를 연결한 문자열에 대해 SHA-1 해시값을 구하여 결과를 Base64 인코딩한다. 이 값을 패스워드 다이제스트라고 부른다.
클라이언트 Authorization 헤더에 WSSE와 profile ="Username-Toekn"을 지정하고 X-WSSE 확장 헤더에 패스워드 다이제스트와 nonce, 일시정보를 넣어 요청을 보낸다.
서버 측에서는 디비에 보관하고 있는 사용자의 패스워드를 사용하여 패스워드 다이제스트를 다시 게산하고, 그 값과 클라이언트가 신고한 값이 같으면 인증을 통과시킨다. 패스워드 자체를 네트워크상으로 흘려보내지 않아도 되고 Digest 만큼 복잡하지도 않다.
캐시란, 서버로부터 가져온 리소스를 로컬 스토리지에 저장하여 재사용하는 방법을 말한다. 로컬 스토리지에 캐싱한 데이터 자체를 "캐시"라고 부른다. 클라이언트가 저장한 캐시는 그 캐시의 유효기간 내에 다시 그 리소스에 접근하려했을 때 재사용 된다.
클라이언트는 서버에서 가져온 리소스의 캐시 간으 여부를 조사, 가능하다면 로컬 스토리지에 저장. 어떤 리소스가 캐시가 가능한지는 리소스 취득 시 헤더로 판단한다. 리소스 캐시가 가능한지, 그 유효기간이 언제까지인지를 pragma, Expire, Cache-Control 헤더를 이용하여 서버가 지정한다.
pragma 헤더에 지정할 수 있는 값은 공식적으로 no-cache뿐이다. 이 값은 리소스를 캐시하지 말 것을 나타낸다. 반드시 해당 리소스는 다시 서버에 접속하여 가져와야 한다.
pragma가 캐시를 억제하는 헤더였다면, Expires는 캐시의 유효기한을 나타낸다.
클라이언트가 리소스에 접근 할 때는 캐시가 유효기간 내에 있는지에 따라, 다시 접속할 것인지, 캐시를 이용할 것인지를 결정한다.
리소스를 변경할 가능성이 없는 경우는 캐시의 유효기간을 무한으로 설정하고 싶겠지만, 그런 경우라도 Expires 헤더에는 최장 약 1년 이내로 일시를 넣을 것을 스펙에서는 권장하고 있다.
Pragma 헤더와 Expires 헤더는 HTTP 1.0이 정의한 헤더이다. 간단한 캐시는 이들로 구현가능하지만 복잡한 지정은 할 수 없다. 1.1에서는 Cache-Control 헤더를 추가하여 Pragma 헤더와 Expires 헤더의 기능은 Cache-Control 헤더로 완전히 대용할 수 있다.
예를들어
Pragma: no-cache
Cache-Control: no-cache
둘은 같다.
또한 현재로부터의 상대시간으로 유효기간을 설정할 수 있다. 86400은 86400초 현재로부터 24시간 캐시가 유효하다는 말이 된다.
이 밖에도 다양한 식별자와 값이 들어가는데 이들에 의해서 더욱 섬세하게 캐시를 제어할 수 있다.
클라이언트가 Expires와 Cache-Control 헤더를 검증한 결과, 로컬 캐시를 그대로 재사용할 수 없다고 판단한 경우라도 조건부 GET을 송신하면 캐시를 재사용할 수 있는 가능성이 있다. 조건분 GET은 서버측에 리소스가 클라이언트 로컬 캐시로부터 변경 되었는지 여부를 조사하는 조건을 요청 헤더에 포함하고, 캐시를 그대로 사용할 수 있는지 검토하는 구조이다.
조건부 GET은 리소스가 Last-Modified 헤더 또는 ETag 헤더를 가지고 있을 때 이용할 수 있다.
HTTP 1.1에서의 커다란 신기능이 지속적 접속이다. TCP 커넥션을 확립하여 요청을 송신하고 그에 대한 응답을 반환할 때마다 TCP 커넥션을 끊었다. 이는 비용이 드는 처리이기 때문에 성능에 영향을 준다.
이 문제를 해결하기 위해 서버와 클라이언트 사이의 매 요청과 응답시마다 커넥션을 절단하는 것이 아니라, 계속 접속을 유지하여 모아두는 방법이 개발 되었다. HTTP 1.0에서는 그것을 Keep-Avlive 헤더로 구현했고 HTTP1.1에서는 지속적 접속이 기본 동작으로 되었다.
지속적 접속에서는 클라이언트가 응답을 기다리지 않고 같은 서버에 요청을 송신할 수 있다. 이것을 '파이프라인화'라고 부른다.
좀더 효율적으로 메세지를 처리할 수 있게 되었고, 커넥션을 끊고 싶을 때는 요청의 Connection 헤더에 close라는 값을 지정하면, 요청의 응답이 반환된 후 접속을 끊는다 라는 의도가 서버에게 전달된다.
표준은 아니지만 자주 사용되는 헤더를 간단히 알아보자
Content-Disposition 헤더는 서버가 클라이언트에 대해 그 리소스의 파일명을 제공하기 위해 이용하는 응답헤더이다. 역시 전자메일 스펙에서 가져온 것이고 때문에 문자 인코딩 방식에 혼란이 있다.
클라이언트가 Atom의 엔트리를 POST할 때 새로 생성할 리소스의 URI의 힌트가 되는 문자열을 서버에게 제시할 수 있다.
Slug 헤더에서는 비 ASCII 문자열의 인코딩으로 UTF-8의 %인코딩을 채용했다.
HTTP 헤더에 대해 개념에서 이용법까지 알아보았다. 헤더는 메서드와 스테이터스 코드를 조합하여 인증이나 캐시 같은 HTTP의 중요한 기능을 구현하였다. 전자메일이나 언어 태그, 문자 인코딩 등 다른 표준을 적극적으로 활용하는 것도 HTTP 헤더의 특징이다. HTTP 헤더를 제대로 사용하기 위해서는 이들의 역사와 실제 서버와 브라우저의 구현 방식에 대해 조사할 수 있는 능력이 필요하다.