본문 바로가기

개발 서적/웹을 지탱하는 기술

[웹을 지탱하는 기술] chatper 09. HTTP 헤더


[목차]
chapter 01. 웹이란 무엇인가?

chapter 02. 웹의 역사
chapter 03. REST 웹의 아키텍처 스타일
chapter 04. URI의 스펙
chapter 05. URI의 설계
chapter 06. HTTP의 기본
chapter 07. HTTP 메서드

chapter 08. 스테이터스 코드
chapter 08. HTTP 헤더


 

HTTP 헤더의 중요성

  • 헤더는 메시지의 바디에 대한 부가적인 정보, 즉 메타 데이터를 표현함
  • 클라이언트와 서버는 헤더를 보고 메시지에 대한 동작을 결정함
  • 리소스에 대한 접근권한을 설정하는 인증이나 클라이언트와 서버의 통신횟수와 양을 감소시키는 캐시 같은 HTTP의 기능을 헤더로 실현함

 

HTTP 헤더의 탄생

  • HTTP의 최초 버전인 0.9에는 헤더가 없었음.
  • HTTP의 스펙 책정이 진행됨에 따라 문서의 메타 데이터를 표현하기 위해 전자메일의 메시지 헤더 형식을 빌려오는 식으로 추가되었음 (Content-Type, Content-Length, Date 헤더 등)
  • 때문에 HTTP의 헤더에는 전자메일의 스펙(RFC 881)의 심플한 포맷으로의 장점과 함께 부적절한 사용예도 존재함 (라틴 알파멧을 위한 문자 인코딩인 ISO 8859-1 이외의 문자가 들어갈 수 없음)
  • 전자메일 프로토콜과 HTTP의 가장 큰 차이는 메일 프로토콜은 한 방향으로밖에 메시지를 주고 받지 않는 것에 비해, HTTP는 한 번의 통신으로 요청/응답의 두 가지 메시지를 주고 받는다는 점 → 전자 메일에 없는 다양한 헤더가 추가됨

 

날짜와 시간

날짜와 시간을 가지는 헤더로 Date와 Expires가 이에 해당함

// 그리니치 표준시(GMT)를 기준으로 나타냄. 
// 한국시간(KST)으로 계산하기 위해서는 9시간을 더하면 된다.
date: Fri, 15 Apr 2022 09:01:57 GMT

이용하는 메시지 헤더 의미

요청과 응답 Date 메시지를 생성한 일시
요청 If-Modified-Since 조건부 GET으로 리소스의 갱신일시를 지정할 대 이용한다
  If-Unmodified-Since 조건부 PUT, 조건부 DELETE로 리소스의 갱신일시를 지정할 때 이용한다
응답 Expires 응답을 캐시할 수 있는 기한
  Last-Modified 리소스를 마지막으로 갱신한 일시
  Retry-After 다시 요청을 전송할 수 있는 일시의 기준

메일의 헤더는 타임 존을 허가하지만, HTTP에서는 GMT로 기술하도록함으로써 섬머타임 등의 복잡한 문제를 회피할 수 있음.

 

MIME 미디어 타입

메시지로 주고받는 리소스 표현의 종류를 지정하는 것.

  • Multipurpose Internet Mail Extensions라는 이름대로 전자메일에서 차용해온 스펙임
  • 오리지널 MIME은 복수의 메일헤더를 정의하는 스펙이지만, HTTP는 그 중 Content-Type 헤더 등 몇 개만을 이용

Content-Type - 미디어 타입을 지정한다

그 메시지의 바디 내용이 어떠한 종류인가를 미디어 타입으로 나타냄

Content-Type: application/xhtml+xml; chartset=utf-8
  • application/xhtml+xml - 미디어 타입. / 왼편을 타입이라 부르고, 오른편을 서브타입이라고 부름
  • 타입의 종류는 임의로 늘릴 수 없음.
  • 서브타입은 비교적 자유롭게 늘릴 수 있음.
  • 서브타입에 ‘+xml’은 접미어. XML의 미디어 타입을 규정의 RFC 3023이 정의하고 있음. XML 미디어 타입에는 반드시 붙여 XML을 판단할 수 있도록 되어 있음

타입 의미 예

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 파라미터를 가질 수 있다.

  • 앞의 예에서는 charset=utf-8로 해당 문서를 UTF-8로 인코딩하는 것을 나타냄
  • charset 파라미터는 생략 가능하지만 타입이 text인 경우 주의해야함
  • → HTTP에서는 text타입의 디폴트 문자 인코딩은 ISO 8859-1로 정의함. 때문에 한글 텍스트가 들어가 있어도, 클라이언트는 ISO 8859-1로 해석해 문자가 깨질 가능성이 있음
  • XML처럼 문서 자체에서 문자 인코딩 방식을 선언할 수 있는 경우라도, text타입의 경우 Content-Type 헤더의 charset 파라미터를 우선하기 때문에 생략하면 ISO 8859-1로 해석함
  • → charset 파라미터를 받드시 붙이는 것이 현 시점에서는 가장 바람직함

 

언어 태그

Content-Language 헤더는 리소스 표현의 자연언어를 지정하는 헤더다. RFC 4646, RFC 4647에서 정의됨

Content-Language: ko-KR
  • ‘-’의 왼편에는 ISO 639가 정의하는 언어코드가 들어간다.
  • ‘-’의 오른편에는 ISO 3166이 정의하는 지역코드가 들어간다.

 

콘텐트 네고시에이션

미디어 타입, 문자 인코딩, 언어 태그는 서버가 일방적으로 결정하는 것뿐만 아니라, 클라이언트와 교섭해서 정할 수도 있다. 이 방법을 Content Negotiation이라고 한다.

Accept - 처리할 수 있는 미디어 타입을 전달한다.

클라이언트가 자신이 처리할 수 있는 미디어 타입을 서버에게 전달할 경우는 Accept 헤더를 이용

Accept: text/html, application/xhtml+xml, application/xml; q=0.9, */*;q=0.8
  • q= : qvalue라고 하며, 해당 미디어 타입의 우선순위를 나타냄.
  • 소수점 이하 세 자리 이내의 0~1까지 수치이며, 수치가 큰 쪽을 우선함
  • 명시되지 않은 경우 디폴트 값인 1을 가짐
  • 클라이언트가 Accept 헤더에 지정한 미디어 타입에 서버가 대응하고 있지 않다면 406 Not Acceptable 반환

Accept-Charset - 처리할 수 있는 문자 인코딩 전달하기

클라이언트가 자신이 처리할 수 있는 문자 인코딩을 서버에 전달할 때 Accept-Charset 헤서 이용

Accept-Charset: EUC-KR;utf-8;q=0.7,*/*;q=0.7
  • EUC-KR과 헤더 기본 문자 인코딩 ISO 8859-1이 qvalue=1로 동일하지만, 구체적으로 명시된 EUC-KR을 우선함

Accept-Language - 처리할 수 있는 언어를 전달한다

클라이언트는 처리할 수 있는 언어 태그를 서버에게 전달하기 위해서 Accept-Language 헤더를 이용

Accept-Language: ko, en-us;q=0.7, en;q=0.3

 

Content-Length와 청크(chunk) 전송

Content-Length - 바디의 길이를 지정한다

메시지가 바디를 가지고 있는 경우, 기본적으로 Content-Length 헤더를 이용해 그 사이즈를 10진수의 바이트로 나타냄. 미리 사이즈를 알고 있는 정적 파일 등의 리소스를 전송할 때는 Content-Length 헤더를 이용하는 것이 간단함

청크 전송 - 바디를 분할하여 전송한다

동적으로 이미지를 생성하는 웹 서비스의 경우, 파일 사이즈가 정해질 때까지 응답할 수 없기 때문에 응답 성능이 저하됨. 이때 사용하는 것이 Transfer-Encoding 헤더.

Transfer-Encoding: chunked
  • Transfer-Encoding 헤더에 Chunked를 지정하면 최종적으로는 사이즈를 모르는 바디를 조금씩 전송할 수 있음
POST / test HTTP/1.1
Host: example.com
**Transfer-Encoding: chunked**
Content-Type: Text/plain; charset=utf-8

**10**
The brow fox ju

**10**
mps quickly over

**e**
the lazy dog.

**0**

  • 각 청크의 시작에는 청크 사이즈가 16진수로 들어감 (16byte → 10, 14byte → e)
  • 청크의 구분을 위해 빈 줄이 들어가며, 마지막에는 반드시 길이가 0인 청크와 빈 줄을 붙이도록 스펙에서 규정함

 

인증

  • 현재 주류인 HTTP 인증 방식에는 HTTP 1.1이 규정하고 있는 Basic 인증과 Digest 인증이 있음.
  • 웹 API에서는 WSSE(WW-Security-Extension)라는 HTTP 인증의 확장 스펙을 사용하는 경우도 있음
  • 어떤 리소스에 액세스 제어가 걸려 있는 경우, 스테이터스 코드 401(Unauthorized)와 WWW-Authenticate 헤더를 이용해, 클라이언트에 리소스 접근에 필요한 인증정보를 통지할 수 있음
  • WWW-Authenticate 헤더에 의해 클라이언트는 서버가 제공하는 방식을 이해할 수 있음
  • 401 Unauthorized는 인증이 실패했음을 나타내는 에러 코드. 패스워드를 틀린 경우 등 실제로 인증할 수 없는 경우에도 반환

Basic 인증

유저 이름과 패스워등에 의한 인증 방식. 유저 이름과 패스워드를 Authorization 헤더에 넣어 요청마다 전송

  • Authorization 헤더의 내용은 유저 이름과 패스워드를 :로 연결하고 Base64인코딩을 한 문자열
  • Base64 인코딩은 간단히 디코딩이 가능하기 때문에 네트워크에 쉽게 노출됨
  • Basic 인증을 사용할 때는 그것이 허용될 정도의 보안 강도로 좋은지, HTTPS 통신을 하여 통신 선로 상에서 암호화 할 것인지 검토해야함

Digest 인증

Basic 인증보다 보안이 강화된 인증 방식.

  • 다이제스트란 메시지 다이제스트의 줄임말로, 어떤 메시지에 대해 해시 함수를 적용한 해시값을 말함
  • Basic 인증보다 다소 복잡한 흐름으로 인증 수행

챌린지

  • Basic 인증에 비해 WWW-Authenticate 헤더의 값이 복잡함 → 이 헤더의 값을 챌린지(Challenge)라고 부름
  • 클라이언트는 챌린지를 사용하여 다음 요청을 조립함
  • nonce : number used once(한번만 사용되는 숫자)의 줄임말. 모든 요청에 대해 변화하는 문자열.
    • 서버 구현에 의존하는 값으로, 기본적으로는 타임스탬프와 서버만 알 수 있는 패스워드를 이용해 생성
    • 타임스탬프는 요청의 유효기간을 좁히기 위해 포함됨
    • nonce는 생성할 해시 값의 보안을 좀 더 강화할 목적으로 이용됨
  • qop : quality of protection(보증의 품질)의 줄임말. ‘auth’나 ‘auth-init’를 지정
    • 클라이언트가 송신할 다이제스트의 작성방법에 영향을 미침
    • auth의 경우는 메서드와 URI로부터 다이제스트를 작성
    • auth-init의 경우는 메서드와 URI에 추가해 메시지 바디도 이용 → POST와 PUT으로 바디를 송신할 때 사용하면 메시지 전체가 변경되지 않음을 보장
  • opaque : 클라이언트에는 불투명한(추측할 수 없는) 문자열로, 동일 URI 공간에 대한 요청에는 공통되게 클라이언트에서 서버로 보냄

다이제스트의 생성과 송신

서버로부터 인증에 필요한 정보를 얻은 클라이언트는 자신의 유저 이름과 패스워드를 이용해 다이제스트를 생성.

  1. 유저 이름, realm, 패스워드는 :로 연결하고, MD5(Message Digest Algorithm 5) 해시 값을 구함
  2. 메서드와 URI의 패스를 :로 연결하고, MD5 해시 값을 구함
  3. 1의 값, 서버로부터 얻은 nonce, 클라이언트가 nonce를 보낸 횟수, 클라이언트가 생성한 nonce, qop 값, 2의 값을 : 로 연결하고, MD5 해시 값을 구한다.

클라이언트는 생성한 다이제스트 값을 Authorization 헤더의 response라는 필드에 넣고 응답을 송신함

Digest 인증의 이점

  • Basic 인증과 달리, 패스워드를 도둑맞을 위험성 없음
  • 서버에 패스워드의 해시 값만 보관하기 때문에, 패스워드 원본 자체를 맡기지 않아 보안 위험을 줄여줌

Disget 인증의 단점

  • 패스워드만 암호화하기 때문에 메시지 자체는 평문으로 전달됨
  • 서버로부터의 nonce가 없다면 클라이언트 쪽에서 다이제스트를 계산할 수 없음. 요청할 때마다 한번은 401 응답을 받아야하는 번거로움이 발생
  • Apache 등의 웹 서버에서 Disget 인증을 지원하지 않을 수 있음

WSSE 인증

  • HTTP 1.1 표준 외의 인증 방식. AtomPub 등의 웹 API의 인증에 사용되고 있음
  • Basic 인증, Digest 인증을 사용할 수 없는 경우의 방법으로 민간에 의해 책정됨
  • WS-Security의 UsernameToken이라는 인증 방식이 베이스가 됨
  • 클라이언트는 패스워드와 자신이 준비한 Nonce와 일시를 연결한 문자열에 대해서 SHA-1 해시 값을 구해, 결과를 인코딩 → 패스워드 다이제스트
  • 클라이언트는 Authorization 헤더에 ‘WSSE’와 ‘profile=”UsernameToken”’을 지정하고 X-WSSE 확장 헤더에 패스워드 다이제스트와 nonce, 일시 정보를 넣어 요청을 보냄
  • 서버 측에서는 데이터베이스 등에 보관하고 있는 사용자의 패스워드를 사용해 패스워드 다이제스트를 다시 계산하고, 결과 값이 같게 되면 인증을 통과 시킴

 

OpenID와 OAuth

지금까지 알아봤던 인증 방식으로는 웹의 발전과 함께 다양한 방식의 요구사항을 실현하는데 한계가 있음.

  • Single Sign-On(통합 인증) : 기존의 인증 방식으로는 웹 서비스별로 계정을 만들어 로그인을 반복해야 함. 사용자가 많은 계정을 만들어야하는 부담이 증가함
  • 웹 서비스 간 사용자 데이터 교환 : 기존의 인증 방식에서는 개인적인 정보는 해당 사용자만 접근할 수 있도록 제한이 걸려 있기 때문에 다른 웹서비스에서는 접근할 수 없음

이런 문제들을 해결하는 것이 OpenID와 OAuth다.

OpenID - 심플한 싱글 사인온

  • OpenID는 심플한 싱글 사인온을 구현한 스펙
  • A 서비스의 계정으로 B 서비스에 로그인할 수 있음
  • 해당 웹 서비스의 계정을 다른 웹 서비스에게 제공하는 쪽을 Identity Provider라 부르고, IdP의 계정을 이용해 독자적인 웹 서비스를 제공하는 쪽을 Service Provide라고 부름
  • OpenID를 이용하면 사용자는 IdP에 가지고 있는 자신의 계정을 SP에 로그인할 수 있음

OAuth - 웹 서비스 사이의 권한의 위임

  • OAuth는 웹 서비스 간 데이터 교환을 위한 스펙
  • 사용자 데이터를 관리하고 제공하는 쪽을 Service Provider, 데이터를 받아서 이용하는 쪽을 Consumer라고 부름
  • 사용자가 Service Provider로부터 Consumer로 데이터를 전달하는 데 동의하면, Service Provider와 Consumers는 데이터를 교환 가능함 → “인가정보를 넘기는 기능”이라고 부름

 

캐시

캐시란 서버로부터 가져온 리소스를 로컬 스토리지에 저장하여 재사용하는 방법으로, HTTP의 중요한 기능 중 하나이다.

캐시용 헤더

  • 클라이언트는 서버에서 가져온 리소스의 캐시 가능 여부를 조사하고 가능한 경우는 로컬 스토리지에 저장
  • 어떤 리소스가 캐시 가능한지는 그 리소스를 취득했을 때의 헤더로 판단
  • 리소스의 캐시 가능 여부, 유효기간은 Pragma, Expire, Cache-Control 헤더를 이용해 서버가 지정함

Pragma - 캐시를 억제한다

  • Pragma 헤더에 지정할 수 있는 값은 공식적으로는 no-cahce 뿐
  • 리소스를 캐시하지 말 것을 나타내는 것으로, 클라이언트가 다음 번에 해당 리소스를 가져올 때는 반드시 서버에 다시 접속하지 않으면 안됨

Expires - 캐시의 유효기간을 나타낸다

  • 캐시의 유효기간을 나타내는 헤더
  • 절대시간으로 유효기간을 표시
  • 최장 약 1년 이내로 일시를 넣을 것을 스펙에서 권장

Cache-Contorl - 상세한 캐시 방법을 지정한다

  • HTTP 1.1에서 추가된 헤더로, Pragma와 Expires 헤더를 완전히 대용함
  • 현재로부터의 상대시간으로 유효기간을 설정할 수 있음

캐시용 헤더의 사용 구분

  • 캐시를 시키지 않을 경우는 Pragma와 Cache-Control의 no-cache를 동시에 지정한다.
  • 캐시의 유효기간이 명확하게 정해져 있는 경우는 Expires를 지정한다.
  • 캐시의 유효기간을 상대적으로 지정하고자 하는 경우는 Cache-Control의 max-age로 상대시간을 지정한다.

조건부 GET

  • 클라이언트가 헤더 검증 결과 로컬 캐시를 재사용할 수 없다고 판단한 경우라도 조건부 GET을 송신하면 캐시를 재사용할 수 있는 가능성이 있음
  • 조건부 GET은 서버 측에 있는 리소스가 클라이언트 로컬의 캐시로부터 변경되어 있는지 조사하는 조건을 요청 헤더에 포함
  • 리소스가 Last-Modified 헤더 또는 ETag 헤더를 가지고 있을 때 이용할 수 있음

If-Modified-Since - 리소스의 갱신일지를 조건으로 한다

If-Modified-Since: Thu, 11 May 2010 16:00:00 GMT
  • 서버의 리소스가 이 이후로 변경되지 않았다면, 서버는 304 Not Modified 상태 코드를 응답. 리소스의 갱신일시는 Last-Modified 헤더로 확인 가능
  • 바디가 포함되지 않기 때문에 그만큼 네트워크 대역을 절약할 수 있음

If-None-Match - 리소스의 ETag를 조건으로 한다

  • 시계를 가지고 있지 않은 서버와 밀리 초 단위로 변경될 가능성이 있는 리소스의 경우 이용
If-None-MNatch: ab3322028
  • If-Modified-Since는 지정한 시간 이후로 갱신되어 있으면이라는 조건이지만, If-None-Match 헤더는 지정한 값과 매치하지 않으면이라는 조건
  • If-None-Match 헤더에 지정하는 값은 캐시하고 있는 리소스의 ETag 헤더의 값
  • 서버상의 리소스가 변경되지 않았다면 304 Not Modified 응답
  • ETag는 리소스 갱신 상태를 비교하기 위해서만 사용하는 문자열.
  • 리소스를 갱신했을 때 다른 값이 되는 것이라면 어떤 문자라도 상관없음

If-Modified-Since와 If-None-Match의 사용 구분

  • 클라이언트 입장에서 서버가 ETag 헤더를 보내고 있다면 If-None-Match 사용. Last-Modified 헤더보다 정확한 갱신 유무를 알 수 있기 때문
  • ETag 헤더가 없고 Last-Modified 헤더밖에 모를 경우 If-Modified-Since 사용

 

ETag의 계산

정적 파일

  • Apache는 기본적으로 정적 파일의 ETag의 값은 inode번호, 파일 사이즈, 갱신 일시를 이용해 자동으로 계산
  • 단, inode번호는 동일 내용의 파일이라도 파일 시스템이 다르면 다른 값으로 식별되기 때문에 분산 서버 환경에서 주의 필요.
  • 이런 경우 파일 사이즈와 갱신 일시만으로 ETag 값을 계산하도록 설계

동적 파일

  • Apache 등의 웹 서버가 ETag를 자동으로 계산해주지 않음
  • HTML과 피드를 생성하는 웹 애플리케이션에서 ETag의 값을 계산해야함
  • 리소스의 메타 데이터(갱신 일시, 사이즈 등)를 이용해 생성하거나, 리소스의 갱신 카운터를 준비해서 대용

 

지속적 접속

  • HTTP 1.0에서는 클라이언트가 TCP 커넥션을 확립해 요청을 송신하고 응답을 받을 때마다 TCP 커넥션을 절단했음
  • TCP 커넥션 확립은 비용이 드는 처리이기 때문에 동작이 느려지는 한계가 있었었음
  • HTTP 1.1에서 서버와 클라이언트 사이의 접속을 유지하여 모아두는 방법 개발
  • HTTP 1.0에서는 Keep-Alive 헤더로 구현했지만, 1.1에서는 기본 동작으로 되었음
  • 클라이언트가 응답을 기다리지 않고 같은 서버에 요청을 송신할 수 있음 → Pipelining
  • 커넥션을 끊고 싶을 때는 요청의 Connection 헤더에 close라는 값을 지정

 

그 밖의 HTTP 헤더

Content-Disposition - 파일명을 지정한다

  • 서버가 클라이언트에 대해 그 리소스의 파일명을 제공하기 위해 이용하는 응답 헤더

Slug - 파일명과 힌트를 지정한다

  • 클라이언트가 Atom의 엔트리를 POST할 때 새로 생성할 리소스의 URI의 힌트가 되는 문자열을 서버에게 제시할 수 있음

 

HTTP 헤더를 활용하기 위해서

  • 헤더는 메서드와 스테이터스 코드를 조합하여 인증이나 캐시 같은 HTTP의 중요한 기능을 구현함
  • HTTP는 전자메일과 문자 인코딩 등 다른 표준을 활용하고 있기 때문에 부적절한 사용을 잘 파악하여 사용해야 함
반응형