👨🏻💻 HTTP의 중요성
헤더는 메시지의 바디에 대한 부가적인 정보, 즉 메타 데이터를 표현
👨🏻💻 HTTP 헤더의 태생
HTTP의 최초 버전 0.9에는 헤더가 없었지만 전자메일의 스펙의 헤더 형식을 빌려오는 식으로 추가되었다.
- HTTP 헤더에도 역시 문자 인코딩 제한이 있어, 라틴 알파벳을 위한 문자 인코딩인 ISO 8859-1 이외의 문자가 들어갈 수 없다.
👨🏻💻 날짜와 시간
날짜와 시간을 가지는 헤더
이용하는 메시지 | 헤더 | 의미 |
---|
요청과 응답 | Date | 메시지를 생성한 일시 |
요청 | If-Modified-Since | 조건부 GET으로 리소스의 갱신일시를 지정할 때 이용 |
요청 | If-Unmodified-Since | 조건부 PUT으로, 조건부 DELETE로 리소스의 갱신ㅇ리시를 지정할 때 이용한다. |
응답 | Expires | 응답을 캐시 할 수 있는 기한 |
응답 | Last-Modified | 리소스를 마지막으로 갱신한 일시 |
응답 | Retry-After | 다시 요청을 전송할 수 있는 일시의 기준 |
- HTTP에 일시는 모두 GMT를 기술하도록 함으로써 섬머타임 등의 복잡한 문제를 회피할 수 있게 되었다.
👨🏻💻 MIME 미디어 타입
메시지로 주고받는 리소스 표현의 종류를 지정하는 것
Content-Type - 미디어 타입을 지정
- 메시지의 바디 내용이 어떠한 종류인가를 미디어 타입으로 나타냄
- ‘/’를 기준으로 왼쪽은 타입, 오른쪽은 서브타입이라 부름
- 타입의 종류는 임의로 늘릴 수 없음
- 서브타입은 비교적 자유롭게 늘릴 수 있음
- 등록된 타입과 서브타입의 목록은 IANA가 관리
타입
타입 | 의미 | 예 |
---|
text | 사람이 읽고 직접 이해할 수 있는 텍스트 | text/plain |
image | 그림 데이터 | image/jpeg |
audio | 음성 데이터 | audio/mpeg |
video | 동영상 데이터 | video/mp4 |
application | 그 밖의 데이터 | application/pdf |
multipart | 복수의 데이터로 이루어진 복합 데이터 | multipart/related |
message | 전자메일 메시지 | message/rfc822 |
model | 복수 차원으로 구성하는 모델 데이터 | model/vrml |
example | 예시용 | example/foo-bar |
charset 파라미터 - 문자 인코딩을 지정
- charset 파라미터는 생략 가능하지만 타입이 text인 경우 주의가 필요
- HTTP에서는 text 타입의 디폴트 문자 인코딩은 ISO 8859-1 이라고 정의하기 때문에 한글 텍스트가 들어가 있음에도 불구하고, 문자가 깨질 가능성이 있다.
- XML처럼 문서 자체에서 문자 인코딩 방식을 선언 할 수 있는 경우라도, text 타입의 경우는 Content-Type 헤더의 charset 파라미터를 우선함
- XML 문서의 경우 text/html을 사용하지 않고, application/xml이나 application/xhtml + xml과 같은 파라미터를 이용하고, 반드시 charset 파라미터를 붙이는 것이 현시점에서는 가장 바람직한 운용방법
👨🏻💻 언어 태그
Content-Language : 리소스 표현의 자연언어를 지정하는 헤더
- ‘-’ 왼쪽에는 ISO 639가 정의하는 언어코드가, 오른편에는 ISO 3166이 정의하는 지역코드가 들어감
👨🏻💻 콘텐트 네고시에이션
클라이언트와 교섭(네고시에이션)해서 미디어 타입과 문자 인코딩, 언어 태그를 정하는 방법
Accept - 처리할 수 있는 미디어 타입을 전달
- 클라이언트가 자신이 처리할 수 있는 미디어 타입을 서버에게 전달
- q=이라는 파라미터의 값을 qvalue라고 합니다.
-
미디어 타입의 우선 순위를 나타냄
-
qvalue는 소수점 이하 세 자리 이내의 0 ~ 1 까지의 수치
Accept: text/html, application/xhtml+xml, application/xml;q=0.9,*/*;q=0.8
-
text/html, application/xhtml+xml은 디폴트인 1
application/xml은 0.9
그 밖의 모든 미디어 타입(/)는 0.8
- 클라이언트가 Accept 헤더에 지정한 미디어 타입에 서버가 대응하고 있지 않다면 406 Not Acceptable이 반환
Accept-Charset - 처리할 수 있는 문자 인코딩 전달
Accept-Language - 처리할 수 있는 언어를 전달
👨🏻💻 Content-Length와 청크(chunk) 전송
메시지의 바디의 사이즈를 10진수의 바이트로 나타냄
- 사이즈를 알고 있는 리소스인 정적인 파일 등을 전송할 때는 10진수의 바이트로 사이즈를 나타냄
청크 전송 - 바디를 분할하여 전송한다
- 동적으로 이미지를 생성하는 웹 서비스의 경우, 파일 사이즈가 정해질 때까지 응답할 수 없기 때문에 응답성능이 저하됨
- → Transfer-Encoding 헤더 사용
- Transfer-Encoding 헤더에 Chunked를 지정하면, 최종적으로는 사이즈를 모르는 바디를 조금씩 전송할 수 있음
- 각 청크의 시작에는 청크 사이즈가 16진수로 들어감
- 청크의 구분을 위해 빈 줄을 사용
- 마지막에는 반드시 길이가 0인 청크와 빈줄을 붙이도록 스펙에서 규정하고 있음
👨🏻💻 인증
- 현재 주류인 HTTP 인증 방식에는 HTTP 1.1이 규정하고 있는 Basic 인증과 Digest 인증이 존재
- 또한, 웹 API에서는 WSSE라는 HTTP 인증의 확장 스펙을 이용하기도 함
- 어떤 리소스에 액세스 제어가 걸려 있는 경우, 스테이터스 코드 401 Unauthorized(이 리소스에 접근하려면 적절한 인증이 필요)와 WWW-Authenticate 헤더를 이용해, 클라이언트에 리소스 접근에 필요한 인증정보를 통지
URI 공간이란?
- URI에서 패스 이하를 가리키는 것
- WWW-Authenticate 헤더의 realm의 값은 이 URI 공간의 이름
Basic 인증
- 유저 이름과 패스워드에 기반한 인증 방식
- 유저 이름과 패스워드는 Authorization 헤더에 넣어 요청마다 전송
- 유저 이름과 패스워드를 ‘:’으로 연결하고 Base64 인코딩을 거쳐 문자열로 변환하여 전송
- Base64는 간단히 디코딩이 가능하다는 점 주의
- Basic 인증을 사용할 때는 그것이 허용될 정도의 보안 강도가 좋은지, SSL와 TLS를 사용해 HTTPS 통신을 하고 통신선로 상에서 암호화할 것인지 검토해야만 함
Digest 인증
- Basic 인증보다 보안이 강화된 인증 방식
- Digest란 어떤 메시지에 대해 해시함수를 적용한 해시값
Digest 인증 과정
Digest 인증에서도 클라이언트는 우선 인증 정보 없이 요청을 전송. 그 결과로서 인증이 실패하고 401 unauthorized 반환
DELETE /test HTTP/1.1
Host: example.com
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digest realm="example.com", nonce="1ac421d9e0a4k7q982z966p903372922", qop="auth", opaque="92eb5ffee6ae2fec3ad71c777531758f"
WWW-Authenticate 헤더의 값을 챌린지 라고 부릅니다. 클라이언트는 챌린지를 사용하여 다음 요청을 조립
nonce(number used once)
- 모든 요청에 대해 변화하는 문자열
- 서버 구현에 의존하는데, 기본적으로는 타임스탬프와 서버만 알 수 있는 패스워드를 이용해 생성
- 타임스탬프가 포함되어 있는 이유는 nonce를 사용한 요청의 유효기간을 좁히기 위함
- 생성할 해시 값의 보안을 좀 더 강화할 목적으로 이용
qop(quality of protection)
- auth 나 auth-init를 지정
- auth
- auth-init
- 메서드와 URI, 메시지 바디로부터 다이제스트를 작성
- POST와 PUT으로 바디를 송신할 때 메시지 전체가 변경되지 않음을 보증
- 클라이언트가 송신할 다이제스트의 작성방법에 영향을 미침
opaque
- 클라이언트에는 불투명한 문자열
- 동일 URI 공간에 대한 요청에는 공통되게 클라이언트에서 서버로 보냅니다
서버로부터 필요한 정보를 얻은 클라이언트는 자신의 유저 이름과 패스워드를 사용해 다이제스트를 생성
Digest 생성 알고리즘
- 유저 이름, realm, 패스워드는 ‘:’로 연결하고, MD5 해시 값을 구함
- 메서드와 URI의 패스를 ‘;’로 연결하고, MD5 해시 값을 구함
- 1 의 값, 서버로부터 얻은 nonce, 클라이언트가 nonce를 보낸 횟수, 클라이언트가 생성한 nonce, qop 값, 2의 값을 ‘:’로 연결하고, MD5 해시 값을 구함
클라이언트는 생성한 다이제스트 값을 response라는 필드에 넣고 전송하고 인증을 통과하고 성공하면 200 OK가 반환
Digest 인증
Pros
- 패스워드를 도둑맞을 위험성은 없다
- 서버에 패스워드의 해시 값만 보관해 두면 되므로, 패스워드 자체를 서버에 맡겨두지 않아도 된다
Cons
- 패스워드만 암호화할 뿐 메시지 자체는 평문으로 네트워크를 흘러감
- 메시지를 암호화하고 싶은 경우 Basic 인증의 경우와 마찬가지로 HTTPS를 이용
- Basic 인증은 같은 URI 공간의 리소스라면 클라이언트는 한번 인증되면 2번째부터는 자동저그올 유저 이름과 패스워드를 전송할 수 있지만 Digest 인증의 경우 서버로부터의 nonce가 없다면 클라이언트 쪽에서 다이제스트를 계산할 수 없기 때문에, 요청할 때마다 한번은 401 Unauthorized 응답을 얻어야만 함
- 클라이언트 입장에서 조작이 번잡하기 때문에 그다지 보급되지 못한 원인
- Apache 등의 웹 서버에서는 Digest 인증이 옵션으로 되어 있어 호스팅 서비스에서는 지원하지 않을 가능성도 있음
- 직접 인증 프로그램을 만들면 되지만 CGI처럼 별도의 프로세스로 동작하는 프로그램에는 보안 문제로 인해 Apache가 인증관련 헤더를 건네주지 않으므로 역시 Digest 인증 이용 불가
WSSE 인증
- HTTP 1.1의 표준 외의 인증 방식
- SSL과 TLS의 이용이 불가능해 Basic 인증을 사용하지 못하고, 호스팅 서비스 상의 CGI 스크립트 등으로 Digest 인증도 사용할 수 없는 경우에 어떻게든 패스워드를 그냥 네트워크로 흘려보내지 않고 인증하는 기구로서 민간에 의해 책정됨
WSSE 인증 과정
클라이언트는 우선 인증 정보 등으로 요청을 보내고, 서버로부터 401 Unauthorized 응답을 받음
DELETE /test HTTP/1.1
Host: example.com
HTTP/1.1 401 Unauthorized
WWW-Authenticate: **WSSE realm="example.com", profile="UsernameToken"**
클라이언트는 패스워드와 자신이 준비한 nonce와 일시를 연결한 문자열에 대해서 SHA-1 해시 값을 구해, 결과를 Base64 인코딩 → 패스워드 다이제스트
클라이언트는 Authorization 헤더에 ‘WSSE’와 ‘profile=”UsernameToken”’을 지정하고 X-WSSE 확장 헤더에 패스워드 다이제스트와 nonce, 일시정보를 넣어 요청을 보낸다.
DELETE /test HTTP/1.1
**Authorization : WSSE profile="UsernameToken"
X-WSSE: UsernameToken Username="test", PasswordDigest="pkkkpksmpkikqqSrpK2krw==", Nonce="88akf2947cd33aa", Created="2010-05-10T09:45:22Z"**
서버 측에서는 데이터베이스 등에 보관하고 있는 사용자의 패스워드를 사용해 패스워드 다이제스트를 다시 계산하고, 그 값과 클라이언트가 신고한 값이 같게 되면 인증 통과
- WSSE 인증은 패스워드 자체를 네트워크상으로 흘려보내지 않아도 되는데다 Digest 인증만큼 복잡하지 않은 반면, 서버 측에서 패스워드를 그냥 보존해 둘 필요가 있는 등 Basic 인증과 Digest 인증의 중간에 위치하는 인증 방식
👨🏻💻 캐시
- 서버로부터 가져온 리소스를 로컬 스토리지(하드디스크 등)에 저장하여 재사용하는 방법
- 로컬 스토리지에 캐싱한 데이터 자체를 “캐시"라고 부르기도 함
- 캐싱된 데이터는 유효 기간 내에서 재사용 가능
캐시용 헤더
- 클라이언트는 서버에서 가져온 리소스의 캐시 가능 여부를 조사하고 가능한 경우는 로컬 스토리지에 저장합니다.
- 어떤 리소스가 캐시 가능한지는 그 리소스를 취득했을 때의 헤더로 판단
Pragma - 캐시를 억제한다
HTTP/1.1 200 OK
Content-Type: application/xhtml+xml, charset=utf-8
**Pragma: no-cache**
...
Expires - 캐시의 유효기한을 나타낸다
HTTP/1.1 200 OK
Content-Type: application/xhtml+xml, charset=utf-8
**Expires: Thu, 11 May 2010 16:00:00 GMT
캐시 가능한 데이터**
- 리소스를 변경할 가능성이 없는 경우, 캐시의 유효기간을 무한으로 설정하고 싶겠지만 그런 경우라도 Expires 헤더에는 최장 약 1년 이내로 일시를 넣을 것을 스펙에서 권장
Cache-Control - 상세한 캐시 방법을 지정한다
- Pragma 헤더와 Expires 헤더의 기능은 Cache-Control 헤더로 완전히 대용 가능
Pragme: no-cache
→
Cache-Control: no-cache
캐시용 헤더의 사용 구분
- 캐시를 시키지 않을 경우는 Pragma와 Cache-Control의 no-cache를 동시에 지정
- 캐시의 유효기간이 명확하게 정해져 있는 경우는 Expires를 지정
- 캐시의 유효기간을 상대적으로 지정하고자 하는 경우는 Cache-Control의 max-age로 상대시간을 지정한다.
조건부 GET
- 조건부 GET은 서버 측에 있는 리소스가 클라이언트 로컬의 캐시로부터 변경되어 있는지 여부를 조사하는 조건을 요청 헤더에 포함시킴으로써, 캐시를 그대로 사용할 수 있는지 검토하는 구조
- 리소스가 Last-Modified 헤더 또는 ETag 헤더를 가지고 있을 때 이용 가능
If-Modified-Since - 리소스의 갱신일시를 조건으로 한다
- 서버의 리소스가 변경되지 않았다면 304 Not Modified를 통해 조건부 GET에 대한 응답이 가능
- 리소스의 갱신일시는 Last-Modified 헤더로 확인
If-None-Match - 리소스의 ETag를 조건으로 한다
- 밀리 초 단위로 변경될 가능성이 있는 리소스에 사용하는 헤더
- 지정한 값과 매치하지 않으면 이라는 조건, If-None-Match 헤더에 지정하는 값은 캐시하고 있는 리소스의 ETag 헤더의 값
- 조건부 GET의 결과, 서버상의 리소스가 변경되어 있지 않으면 ETag 헤더의 값을 반환
- 리소스 갱신의 경우 다른 값이 되는 것이면 어떤 문자라도 상관없이 반환
If-Modified-Since와 If-None-Match의 사용 구분
- 클라이언트 입장에서는 서버가 ETag 헤더를 보내고 있다면, If-None-Match 헤더를 이용하는 편이 좋음(Last-Modified 헤더보다도 정확한 갱신의 유무를 확인할 수 있기 때문)
- 서버를 구현 중이라면, 캐시 가능한 리소스에는 가능한 한 ETag 헤더를 이용, ETag가 없고 Last-Modified 헤더밖에 모를 경우 If-Modified-Since 헤더를 사용
👨🏻💻 지속적 접속
지속적 접속에서는 클라이언트가 응답을 기다리지 않고 같은 서버에 요청을 송신할 수 있습니다. 이것을 ‘파이프라인화(Pipelining)'라고 부릅니다.
- 커넥션을 끊고 싶을 때는 요청의 Connection 헤더에 close라는 값을 지정
👨🏻💻 그 밖의 HTTP 헤더
Content-Disposition - 파일명을 지정한다
- 서버가 클라이언트에 대해 그 리소스의 파일명을 제공하기 위해 이용하는 응답 헤더
Slug - 파일명과 힌트를 지정한다
- 클라이언트가 Atom의 엔트리를 POST할 때 새로 생성할 리소스의 URI의 힌트가 되는 문자열을 서버에게 제시할 수 있습니다.
👨🏻💻 HTTP 헤더를 활용하기 위해서
HTTP 헤더를 제대로 사용하기 위해서는 이들의 역사와 실제 서버와 브라우저의 구현 방식에 대해 조사할 수 있는 능력이 필요