[웹 개발자를 위한 웹을 지탱하는 기술] - HTTP 헤더

김성혁·2022년 4월 25일
0

👨🏻‍💻 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-Charset: EUC-KR;utf-8;q=0.7,*/*;q=0.7
    • EUC-KR와 Accept-Charset 헤더의 기본 문자 인코딩 ISO 8859-1이 qvalue의 기본값인 우선도 1이 되지만, 좀 더 구체적인 EUC-KR를 우선함. 여기서 이어지는 UTF-8 및 그 밖의 모든 인코딩 방식이 0.7인 우선도가 됩니다.

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

  • 클라이언트가 자신이 처리할 수 있는 언어 태그를 서버에게 전달
    Accept-Language: ko, en-us;q=0.7,en;q=0.3
    • ko가 기본값 1, en-us가 0.7, 지역을 특정하지 않는 en가 0.3 우선도를 가집니다.

👨🏻‍💻 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
      • 메서드와 URI로부터 다이제스트를 작성
    • auth-init
      • 메서드와 URI, 메시지 바디로부터 다이제스트를 작성
      • POST와 PUT으로 바디를 송신할 때 메시지 전체가 변경되지 않음을 보증
  • 클라이언트가 송신할 다이제스트의 작성방법에 영향을 미침

opaque

  • 클라이언트에는 불투명한 문자열
  • 동일 URI 공간에 대한 요청에는 공통되게 클라이언트에서 서버로 보냅니다

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

Digest 생성 알고리즘

  1. 유저 이름, realm, 패스워드는 ‘:’로 연결하고, MD5 해시 값을 구함
  2. 메서드와 URI의 패스를 ‘;’로 연결하고, MD5 해시 값을 구함
  3. 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
  • Expires에서는 절대시간으로 유효기간을 표시하는 반면, Cache-Control에서는 현재로부터의 상대시간으로 유효기간을 설정 가능
    Cache-Control: max-age:86400
  • 더욱 섬세하게 캐시를 제어 가능

캐시용 헤더의 사용 구분

  • 캐시를 시키지 않을 경우는 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 헤더를 제대로 사용하기 위해서는 이들의 역사와 실제 서버와 브라우저의 구현 방식에 대해 조사할 수 있는 능력이 필요

0개의 댓글