응용 계층
이번 포스트는 ‘혼자 공부하는 네트워크‘라는 책을 통해 네트워크 참조 모델의 최상위 계층인 응용 계층에 대해 알아보는 목적을 갖고 있다.
우선, 이런 좋은 책을 만들어주신 강민철 님에게 감사하다는 것을 전하고 시작하도록 하겠다. 강민철 님은 어려운 내용들을 쉽게 설명해내는 능력이 매우 띄어난 저자로, 같은 설명인 것 같은데도 머리 속에 더 잘 남게해주신다. 개발 관련해서 처음 배우는 학문이나 여러 번 공부했는데 머리 속에 박혀있지 못한 것이 있다면 강민철 님의 책이나 강의를 추천한다.
그럼 시작해보자.
1. DNS와 자원
클라이언트와 서버의 요청-응답 메세지 송수신 과정 덕분에 우리가 브라우저에 특정 URL을 입력하면 해당 웹 페이지를 확인할 수 있다. 이런 것이 가능하기 위해서는 서버와 클라이언트가 ‘메세지를 주고받고자 하는 대상’과 ‘송수신하고자 하는 정보’를 식별할 수 있어야 한다.
메세지를 주고받고자 하는 대상을 파악하기 위해선 도메인 네임을 사용할 수 있다. 그리고 송수신하고자 하는 정보를 식별하기 위해 위치 기반의 식별자인 URL과 이름 기반의 식별자인 URN를 사용할 수 있다.
1.1 도메인 네임과 네임 서버
네트워크 상의 어떤 호스트를 특정하기 위해 IP주소가 사용되는데, 사용자 입장에서 모든 호스트의 IP주소를 기억하기 어렵고 그러한 IP주소는 언제든지 바뀔 수 있어 사용하기 불편하다.
그래서 일반적으로 사용자는 상대 호스트를 특정하기 위해 도메인 네임을 많이 사용한다. 이는 IP 주소와 대응되는 문자열 형태의 호스트 특정 정보이다.
전화번호부처럼 ‘사용자 이름:전화번호’ 쌍의 목록을 모아 관리하는 것처럼, 도메인 네임과 IP 주소는 네임 서버에서 관리한다. 도메인 네임을 관리하는 네임 서버는 DNS 서버라고도 부른다.
도메인 네임은 점(.)을 기준으로 계층적으로 분류된다. 최상단에 루트 도메인이 있고, 그 다음 단계인 최상위 도메인$^{TLD}$이 있으며, 계속 그 다음 단계의 도메인이 있는 식이다.
우리가 일반적으로 알고 있는 도메인 네임의 마지막 부분을 최상위 도메인, 줄여 TLD라고 한다. 예를 들어 www.example.com의 최상위 도메인은 ‘com’이다.
흔히 최상위 도메인을 도메인 네임의 마지막 부분이라고 생각하기 쉽지만, 사실 루트 도메인도 도메인 네임의 일부다. 루트 도메인은 점(.)으로 표현되고 도메인 네임의 마지막에 점이 찍힌 형태로 표기된다. 일례로 웹 브라우저에서 www.google.com. 과 같은 도메인 네임으로 접속해도 잘 접속된다. 다만 일반적으로 루트 도메인을 생략해서 표기하기 때문에 대부분 최상위 도메인이 마지막 부분이라고 간주한다.
최상위 도메인의 하부 도메인은 2단계 도메인이라고 한다. 예를 들어 www.example.com 에서는 ‘example’이 2단계 도메인이 된다.
나아가 www.example.com에서 ‘www’는 3단계 도메인이다. 도메인 단계는 이보다 더 늘어날 수도 있지만, 일반적으로 3~5단계 정도다. 그리고 www.example.com. 처럼 도메인 네임을 모두 포함하는 도메인 네임을 전제 주소 도메인 네임$^{FQDN}$이라고 한다.
도메인 네임의 첫번째 부분을 호스트 네임이라 부르기도 한다.
이렇게 계층적인 형태를 띠는 도메인 네임을 효율적으로 관리하기 위해 네임 서버도 계층적인 형태를 이룬다. 또 네임 서버는 여러 개 존재하고 전 세계 여러 군데에 위치해 있다. 이렇게 계층적이고, 분산된 도메인 네임에 대한 관리 체계를 도메인 네임 시스템, 줄여서 DNS라고 부른다. DNS는 호스트가 이러한 도메인 네임 시스템을 이용할 수 있도록 하는 애플리케이션 계층 프로토콜을 의미하기도 한다.
1.2 계층적 네임 서버
계층적인 네임 서버들의 구성을 알아보고, 그걸 토대로 도메인 이름으로 IP 주소를 알아내는 과정을 이해해보자.
그런 과정에는 다양한 네임 서버들이 사용되는데, 중요한 역할을 하는 네임 서버의 유형은 크게 네 가지가 있다. 바로 ‘로컬 네임 서버’, ‘루트 네임 서버’, ‘TLD 네임 서버’, ‘책임 네임 서버’다.
로컬 네임 서버는 클라이언트와 맞닿아 있는 네임 서버로, 클라이언트가 도메인 네임을 통해 IP 주소를 알아내고자 할 때 가장 먼저 찾게 되는 네임 서버다. 로컬 네임 서버의 주소는 일반적으로 ISP에서 할당해 주는 경우가 많다. 그런데 클라이언트가 로컬 네임 서버 대신에 공개 DNS 서버를 이용할 수도 있다.
클라이언트가 로컬 네임 서버에게 특정 도메인 네임에 대응되는 IP주소가 무엇인지 질의했다고 가정하자. 그런데 로컬 네임 서버가 그 도메인에 대응되는 IP 주소를 모른다면 루트 네임 서버에게 해당 도메인 네임을 질의하게 된다. 루트 네임 서버는 루트 도메인을 관장하는 네임 서버로, 질의에 대해 TLD 네임 서버의 IP 주소를 반환할 수 있다.
TLD 네임 서버는 TLD를 관리하는 네임 서버로, 하위 도메인 네임을 관리하는 네임 서버 주소를 반환할 수 있다. 그리고 하위 도메인 네임을 관리하는 네임 서버도 그보다 하위 도메인 네임을 관리하는 네임 서버 주소를 반환할 수 있다.
책임 네임 서버는 특정 도메인 영역을 관리하는 네임 서버로, 자신이 관리하는 도메인 영역의 질의에 대해서는 다른 네임 서버에게 곧바로 답할 수 있는 네임 서버다. 쉽게 말해, 로컬 네임 서버가 마지막으로 질의하는 네임 서버다. 일반적으로 로컬 네임서버는 책임 네임 서버로부터 원하는 IP 주소를 얻어낸다.
로컬 네임 서버가 네임 서버들에게 질의하는 방법에는 크게 ‘재귀적 질의’와 ‘반복적 질의’라는 두가지 방법이 있다. www.example.com의 IP 주소를 알아내련느 상황을 가정해보자.
재귀적 질의는 클라이언트가 로컬 네임 서버에게 도메인 네임을 질의하면, 로컬 네임 서버가 루트 네임 서버에게 질의하고, 루트 네임 서버가 TLD 네임 서버에게 질의하고, TLD 네임 서버가 다음 단계에 질의하는 과정을 반복하며 최종 응답 결과를 역순으로 전달받는 방식이다.
반복적 질의는 클라이언트가 로컬 네임 서버에게 IP 주소를 알고 싶은 도메인 네임을 질의하면, 로컬 네임 서버는 루트 도메인 서버에게 질의해서 다음으로 질의할 네임 서버의 주소를 응답받고, 다음으로 TLD 네임 서버에게 질의해서 다음으로 질의할 네임 서버의 주소를 응답받는 과정을 반복하다가 최종 응답 결과를 클라이언트에게 알려주는 방식이다.
지금까지 살펴본 도메인 네임 리졸빙 과정에서는 약간의 문제가 있다. 하나의 도메인 네임을 리졸빙하기 위해 8개의 단계를 거쳐야 하는 것처럼 시간이 오래걸리고 네트워크상의 메세지 수가 지나치게 늘어날 수 있다는 점이다. 만약 전 세계 모든 호스트가 도메인 네임 리졸빙을 위해 루트 네임 서버에 도메인 네임을 한꺼번에 질의한다면 루트 네임 서버에 과부하가 생길 것이다.
그래서 실제로는 네임 서버들이 기존에 응답받은 결과를 임시로 저장했다가 나중의 같은 질의에 이를 활용하는 경우가 많다. 이를 DNS 캐시라고 한다. DNS 캐시를 저장하는 용도로만 사용되는 서버도 있다. 그리고 이 캐시는 TTL이라는 값과 함게 저장되어, 일정 시간이 지나면 삭제되며 관리된다.
1.3 자원을 식별하는 URI
송수신하고자 하는 정보를 식별하기 위한 방식인 URI와. URI를 식별 정보 기준으로 분류한 개념인 URL, URN을 살펴보자. 이 개념들을 이해하려면 우선 자원이 무엇인지 부터 이해해야 한다. 자원이란 네트워크상의 메세지를 통해 주고받는 대상을 의미한다. 이는 HTML 파일, 이미지, 동영상 파일, 텍스트 파일 같은 것이 될 수 있다.
오늘날 인터넷 환경을 이루는 대부분의 통신은 HTTP를 기반으로 이루어지므로, 자원이라는 용어는 ‘HTTP 요청 메세지의 대상’이라고도 표현된다.
네트워크상에서 자원을 주고받으려면 자원을 식별할 수 있어야 한다. 자원을 식별할 수 있는 정보를 URI라고 부른다. 이름 그대로 자원을 식별하는 통일된 방식이 URI인 셈이다.
URI가 자원을 식별할 땐 위치를 이용할 수도 있고, 이름을 이용할 수도 있다. 전자를 URL, 후자를 URN이라고 한다.
1.3.1 URL
그 중에 더 많이 사용되는 방법은 URL이다.
인터넷 표준 문서(RFC 3986)에서 소개하는 URL 형태를 살펴보며 HTTP(S)에서 어떻게 표기되고 사용되는지를 기준으로 설명해보려 한다.
1
foo://www.example.com:8042/over/there?name=ferret#nose
- foo: scheme
URL의 첫 부분은 scheme이다. scheme은 ‘자원에 접근하는 방법’을 의미한다. 여기에 일반적으로 사용할 프로토콜이 명시된다. HTTP를 사용해서 자원에 접근할 때는 http:// 를사용하고, HTTPS를 사용해 자원해 접근할 때는 https://를 사용한다.
- www.example.com:8042 : authority
여기엔 ‘호스트를 특정할 수 있는 정보’, 이를테면 IP 주소나 도메인 네임이 명시된다. 콜론 뒤에 포트 번호를 덧붙일 수도 있다.
- over/there: path
여기엔 ‘자원이 위치한 경로’가 명시된다. 자원의 위치는 슬래시를 기준으로 계층적으로 표현되고, 최상위 경로 또한 슬래시로 표현된다. 운영체제의 디렉토리 구조를 따라 폴더 안의 폴더가 계층적으로 표현되듯이 표현된다.
- ?name=ferret: query
HTTP는 요청-응답 기반의 프로토콜이다. 클라이언트는 서버에게 URI(URL)가 포함된 HTTP 요청 메세지를 보내고, HTTP 서버는 이에 대해 HTTP 응답 메세지를 보낸다.
지금까지 알아본 URL 구문만으로도 문제없이 자원의 위치를 식별할 수 있는 경우도 있지만, 때론 더 많은 정보가 필요할 수 있다. 예를 들어, 수많은 정보 중에서 ‘특정 단어를 검색한 결과’에 해당하는 자원이나 수많은 상품 중에서 ‘특정 상품을 검색한 뒤 그 결과를 내림차순으로 정렬한 결과’에 해당하는 자원을 표현해야 할 수 있다.
이럴 때 사용할 수 있는 게 쿼리 문자열(또는 쿼리 파라미터)이다. 이 것은 물음표로 시작되는 <키=값> 형태의 데이터로, 앰퍼샌드(&)를 사용해 여러 쿼리 문자열을 연결할 수 있다.
- #nose: fragment
fragment는 ‘자원의 한 조각을 가리키기 위한 정보’다. 흔히 HTML 파일과 같은 자원에서 특정 부분을 가리키게 위해 사용된다. 이 페이지의 오른쪽에 있는 북마크가 바로 fragment다.
2. HTTP
HTTP에는 중요한 네 가지 특성이 있다.
- 요청과 응답을 기반으로 동작한다.
- 미디어 독립적이다.
- 상태를 유지하지 않는다.
- 지속 연결을 지원한다.
2.1 HTTP의 특성
HTTP는 응용 계층에서 정보를 주고받는 데 사용되는 프로토콜이다.
2.1.1 요청-응답 기반 프로토콜
HTTP는 서버와 클라이언트가 요청 메세지와 응답 메세지를 주고받는 구조로 동작한다. 그래서 같은 HTTP 메세지라고 하더라도 HTTP 요청 메세지와 HTTP 응답 메세지는 메세지 형태가 다르다.
2.1.2 미디어 독립적 프로토콜
HTTP를 정의한 공식 문서(RFC 9110)에서는 다음과 같이 이야기한다.
‘HTTP가 요청하는 대상을 자원이라고 한다. HTTP는 자원의 특성을 제한하지 않으며, 단지 자원과 상호 작용하는 데 사용할 수 있는 인터페이스를 정의할 뿐이다. 대부분의 자원은 URI로 식별된다.’
다시 말해 HTTP는 주고받을 자원의 특성과 무관하게 그저 자원을 주고받을 수단(인터페이스)의 역할만을 수행한다.
HTTP에서 메세지로 주고받는 자원의 종류를 미디어 타입이라고 부른다. 또는 MIME 타입이라고도 부른다. 미디어 타입은 기본적으로 슬래시를 기준으로 하는 ‘타입/서브타입’ 형식으로 구성된다. 타입은 데이터 유형을 나타내고, 서브타입은 주어진 타입에 대한 세부 유형을 나타낸다.
또한 미디어 타입에는 부가적인 설명을 위해 선택적으로 매개변수가 포함될 수도 있다. 매개변수는 ‘타입/서브타입;매개변수=값’의 형식으로 표현된다.
예를 들어, type/html;charset=UTF-8은 미디어 타입이 HTML 문서 타입이며, HTML 문서 내에서 사용된 문자는 UTF-8로인코딩 되었음을 의미한다.
2.1.3 스테이트리스 프로토콜
HTTP는 상태를 유지하지 않는 스테이트리스 프로토콜이다. 이는 서버가 HTTP 요청을 보내온 클라이언트와 관련된 상태를 기억하지 않는다는 의미다. 그렇기 때문에 클라이언트의 모든 HTTP 요청은 기본적으로 독립적인 요청으로 간주된다.
이 특성은 효율적이지 않아 보일 수도 있지만, 실제론 장점이 더 명확하다. HTTP 서버는 일반적으로 많은 클라이언트와 동시에 상호 작용한다. 이러한 상황에서 모든 클라이언트의 상태 정보를 유지하는 것은 서버에 큰 부담이 되기 때문이다.
또한, 서버는 여러 대로 구성될 수도 있다. 이런 상황에서 모든 서버가 모든 클라이언트의 상태를 유지할 경우 클라이언트는 여러 서버를 동시에 이용하기가 어려워진다. 모든 서버가 모든 클라이언트의 상태 정보를 공유하는 작업은 매우 번거롭고 복잡하기 때문이다.
HTTP가 처음 만들어졌을 때부터 오늘날까지 이어지는 중요한 설계 목표는 바로 확장성과 견고성이다.
2.1.4 지속 연결 프로토콜
HTTP는 지속해서 발전 중인 프로토콜인 만큼, 여러 버전이 있다. 오늘날 많이 사용되는 버전은 HTTP 1.1과 2.0이 있다. 기본적으로 HTTP는 TCP 상에서 동작하는데, HTTP는 비연결형 프로토콜이지만, TCP는 연결형 프로토콜이다. 따라서 초기의 HTTP에서는 응답을 받고난 후에 연결이 종료되어서 추가적인 요청-응답마다 다시 TCP 연결을 해야 했다. 이는 네트워크적으로 비효율적이기 때문에 최근 사용되는 HTTP 버전에서는 지속 연결(or 킵 얼라이브)이라는 기술을 제공한다.
2.2 HTTP 메세지 구조
HTTP 메세지는 시작 라인, 필드 라인, 메세지 본문으로 이루어져 있다. 필드 라인은 없거나 여러 개 있을 수 있고, 메세지 본문은 없을 수도 있다. 또한 필드 라인과 메세지 본문 사이에는 빈 줄바꿈이 있다.
2.2.1 시작 라인
먼저 시작 라인은 HTTP 메세지가 요청 메세지이면 따라 요청 라인이 되고, 응답 메세지이면 상태 라인이 된다.
요청 라인의 형식은 다음과 같다.
1
메서드 (공백) 요청 대상 (공백) HTTP 버전 (줄바꿈)
메서드란 클라이언트가 서버의 자원에 대해 수행할 작업의 종류를 나타낸다.
요청 대상은 HTTP 요청을 보낼 서버의 자원을 의미한다. 보통 쿼리가 포함된 URI의 경로가 명시된다. 만약 하위 경로가 없어도 요청 대상은 슬래시로 표기해야 한다.
HTTP 버전은 사용된 HTTP의 버전을 의미한다.
다음으로, 상태 라인의 형식은 다음과 같다.
1
HTTP 버전 (공백) 상태 코드 (공백) 이유 구문* (줄바꿈)
HTTP 버전은 HTTP의 버전이고, 상태 코드는 요청에 대한 결과를 나타내는 세 자리 정수다. 클라이언트는 이를 통해 요청이 어떻게 처리되었는지 판단할 수 있다. 이유 구문은 상태 코드에 대한 문자열 형태의 설명을 의미한다.
2.2.2 필드 라인
이제는 필드 라인을 살펴보겠다. 필드 라인에는 0개 이상의 HTTP 헤더가 명시된다. 그래서 이를 헤더 라인이라고도 부른다. 여기서 HTTP 헤더란 HTTP 통신에 필요한 부가 정보를 의미한다. 참고로, 공식 문서에서는 필드 라인에 ‘0개 이상’의 HTTP 헤더가 명시된다고 언급되지만, 실제로는 한 HTTP 메세지에 아주 다양한 HTTP 헤더들이 사용되는 것이 일반적이다.
필드 라인에 명시되는 각 HTTP 헤더는 콜론(:)을 기준으로 헤더 이름과 하나 이상의 헤더 값으로 구성된다.
2.2.3 메세지 본문
HTTP 요청 혹은 응답 메세지에서 본문이 필요할 경우 이는 메세지 본문에 명시된다. 메세지 본문은 존재하지 않을 수도 있고, 다양한 콘텐츠 타입이 사용될 수도 있다.
2.3 HTTP 메서드
HTTP 요청 메세지에서 사용될 수 있는 메서드는 GET, HEAD, POST, PUT, PATCH, DELETE, CONNECT, OPTIONS, TRACE 가 있다.
HTTP 메서드 | 설명 |
---|---|
HEAD | GET과 동일하지만, 헤더만을 응답받는 메서드 |
CONNECT | 자원에 대한 양방향 연결을 시작하는 메서드 |
OPTIONS | 사용 가능한 메서드 등 통신 옵션을 확인하는 메서드 |
TRACE | 자원에 대한 루프백 테스트를 수행하는 메서드 |
2.4 HTTP 상태코드
상태 코드 | 설명 |
---|---|
100번대 | 정보성 상태 코드 |
200번대 | 성공 상태 코드 |
300번대 | 리다이렉션 상태 코드 |
400번대 | 클라이언트 에러 상태 코드 |
500번대 | 서버 에러 상태 코드 |
2.4.1 200번대: 성공 상태 코드
대표적인 200번대 상태 코드는 다음과 같다.
상태 코드 | 이유 구문 | 설명 |
---|---|---|
200 | OK | 요청이 성공했음 |
201 | Created | 요청이 성공했으며, 새로운 자원이 생성되었음 |
202 | Accepted | 요청을 잘 받았으나, 아직 요청한 작업을 끝내지 않았음 |
204 | No Content | 요청이 성공했지만, 메세지 본문으로 표시할 데이터가 없음 |
2.4.2 300번대: 리다이렉션 상태 코드
리다이렉션은 인터넷 공식 문서(RFC 9110)에서 ‘요청을 완수하기 위해 추가적인 조치가 필요한 상태’로 정의되어 있다. 이를 직접적으로 표현해보자면, 클라이언트가 요청한 자원이 다른 곳에 있을 때 그곳으로 이동시키는 것을 의미한다. 클라이언트가 요청한 자원이 다른 URL 에 있으면 서버는 응답 메세지의 Location 헤더를 통해 요청한 자원이 위치한 URL을 안내해 줄 수 있다.
리다이렉션의 유형은 크게 영구적인 리다이렉션과 일시적인 리다이렉션으로 구분된다. 전자는 자원이 완전히 새로운 곳으로 이동해 경로가 영구적으로 재지정되는 것을 의미한다. 따라서 이 경우 기존의 URL에 요청 메세지를 보내면 항상 새로운 URL로 리다이렉트된다. 이와 관련한 상태 코드로는 301(Moved Permanently)과 308(Permanent Redirect)이 있다.
상태 코드 301과 308의 차이점은 ‘ 클라이언트의 재요청 메서드 변경 여부’에 있다. 301은 바뀔 수도 있고, 308은 바뀌지 않는다.
먼저 재요청 메서드가 변경되지 않는 경우를 살펴보자면, GET 요청의 응답으로 301이나 308 응답 메세지를 받아 Location 헤더에 명시된 경로로 다시 GET 요청을 보내는 걸 생각해볼 수 있다.
하지만 GET 요청이 아닌 POST 메서드같은 요청 메서드를 보냈을 때, 301 응답 메세지를 받았다고 하면 클라이언트가 보내는 두 번째 요청 메서드는 GET 요청으로 바뀔 ‘수도’ 있다. 이러한 애매함으로 인해 308 상태 코드가 등장했다. 클라이언트가 308 응답 메세지를 받을 경우, 두 번째 요청 메서드는 변하지 않는다.
일시적인 리다이렉션은 자원의 위치가 임시로 변경되었거나 임시로 사용할 URL이 필요한 경우에 주로 사용된다. 따라서 어떤 URL에 의해 일시적인 리다이렉션 관련 상태 코드를 응답받았다면 여전히 요청을 보낸 URL은 기억해야 한다. 일시적인 리다이렉션과 관련한 상태 코드는 302(Found), 303(See Other), 307(Temporary Redirect)이 있다.
상태 코드 302는 301과 유사하다. 301이 ‘요청한 자원이 완전히 다른 곳으로 이동했음’을 나타낸다면 302는 ‘요청한 자원이 임시로 다른 곳으로 이동했음’을 나타낸다는 정도의 차이가 있다. 그리고 301이 그랬듯 302도 클라이언트의 재요청 메서드가 변경될 수 있다.
상태코드 301의 애매모호함을 해결하기 위해 상태 코드 308이 있는 것 처럼, 302의 애매모호함을 해결하기 위해 307이 있다. 307은 두 번째 요청 메서드를 변경하지 않는 상태 코드다. 반대로 두 번째 요청 메서드를 변경하기 위해서는 303(See Other) 상태 코드가 사용된다.
2.4.3 400번대: 클라이언트 에러 상태 코드
400번대 상태 코드는 ‘클라이언트에 의한 에러가 있음’을 알려주는 상태 코드다. 대표적인 400번대 상태 코드는 다음과 같다.
상태코드 | 이유 구문 | 설명 |
---|---|---|
400 | Bad Request | 클라이언트의 요청이 잘못되었음 |
401 | Unauthorized | 요청한 자원에 대한 유요한 인증이 없음 |
403 | Forbidden | 요청이 서버에 의해 거부됨(예: 접근 권한이 없을 경우) |
404 | Not Found | 요청받은 자원을 찾을 수 없음 |
405 | Method Not Allowed | 요청한 메서드를 지원하지 않음 |
이 중 401과 403을 혼동하기 쉬운데, 인증 여부와 권한 부여 여부는 다른 개념이다. 인증이란’ 자신이 누구인지 증명하는 것’을 의미하고, 권한 부여는 ‘인증된 주체에게 작업을 허용하는 것’을 의미한다. 권한 부여는 ‘인가’라고도 부른다. 따라서 인증이 되었더라도 권한이 충분하지 않을 수 있다.
2.4.4 500번대: 서버 에러 상태 코드
400번대 상태 코드의 원인이 클라이언트라면, 500번대 상태 코드의 원인은 서버다. 대표적인 500번대 상태 코드는 다음과 같다.
상태코드 | 이유 구문 | 설명 |
---|---|---|
500 | Internal Server Error | 요청을 처리할 수 없음 |
502 | Bad Gateway | 중간 서버의 통신 오류 |
503 | Service Unavailable | 현재는 요청을 처리할 수 없으나 추후 가능할 수도 있음 |
3. HTTP 헤더와 HTTP 기반 기술
이번 절에서는 HTTP 메세지의 두 번째 줄에 해당하는 필드 라인을 학습하자.
HTTP의 중요 헤더 중에는 특별한 사전 지식이 필요하지 않은 헤더가 있고, 필요한 헤더가 있다. 우선 중요하지만 별도의 사전 지식이 없어도 이해할 수 있는 것들부터 살펴보고, 필요한 것을 살펴보는 순서로 학습하겠다.
3.1 HTTP 헤더
헤더의 종료는 매우 많기 때문에 자주 활용되는 중요한 HTTP 헤더 위주로 학습해보겠다.
HTTP 요청 시 주로 사용되는 헤더, HTTP 응답 시 주로 사용되는 헤더, 그리고 HTTP 요청과 응답 모두에서 자주 활용되는 헤더 순으로 설명하겠다.
3.1.1 요청 시 활용되는 HTTP 헤더
- Host
Host는 요청을 보낼 호스트를 나타내는 헤더이다. 주로 도메인 네임으로 명시되고, 포트 번호가 포함되어 있을 수 있다.
- User-Agent
User-Agent는 Host 헤더와 더불어 HTTP 요청 메세지에서 가장 흔히 볼 수 있는 헤더 중 하나다. 유저 에이전트란 웹 브라우저와 같이 HTTP 요청을 시작하는 클라이언트 측의 프로그램을 의미한다.
그래서 이 헤더에는 요청 메세지 생성에 관여한 클라이언트 프로그램과 관련된 다양한 정보가 명시된다. 서버는 이 헤더를 통해 클라이언트의 접속 환경을 유추할 수 있다.
- Referer
Referer는 개발 시 아주 유용한 헤더 중 하나다. 이 헤더에는 클라이언트가 요청을 보낼 때 머무르고 있던 URL이 명시된다. 그래서 이 정보를 통해 클라이언트이 유입 경로를 파악해볼 수 있다.
참고로, 영문법적으로는 Referrer가 맞지만, 초기 개발 당시의 오타로 인해 오늘날까지 이렇게 사용되고 있다. Referer라는 표기를 보고 혼란이 없기를 바란다.
- Authorization
Authorization 헤더는 클라이언트의 인증 정보를 담는 헤더다. 이 헤더에 인증 정보를 담을 땐 인증 타입과 인증을 위한 정보가 차례로 명시된다. 인증 타입에 따라 인증 정보에 명시될 값이 달라진다.
1
Authorization: <type> <credentials>
인증 타입의 종류는 다양하지만, 가장 기본적인 HTTP 인증 타입은 Basic이라는 타입이다. 이 타입 인증은 username:password 와 같이 콜른을 이용해 두 정보를 합친 뒤, 이를 Base64 인코딩한 값을 인증 정보로 삼는 방식이다. 여기서 Base64 인코딩은 ‘문자를 코드로 변환하는 방법’을 의미하는 인코딩 방식의 일종이다.
3.1.2 응답 시 활용되는 HTTP 헤더
이번에는 HTTP 응답 시 주로 활용되는 대표적인 헤더인 ‘Server’, ‘Allow’, ‘Retry-After’, ‘Location’, ‘WWW-Authenticate’ 에 대해 알아보겠다.
- Server
Server 헤더는 요청을 처리하는 서브 측의 소프트웨어와 관련된 정보를 명시한다. 예를 들어 다음 예시 헤더는 ‘Unix 운영체제에서 동작하는 아파치 HTTP 서버’를 의미한다.
1
Server: Apache/2.4.1 (Unix)
- Allow
Allow 헤더는 클라이언트에게 허용된 HTTP 메서드 목록을 알려주기 위해 사용된다. 앞선 절에서 학습한 상태 코드 405(Method Not Allowed)와 함께 이 헤더가 사용된다.
- Retry-After
2절 HTTP 에서 상태 코드 503(Service Unavailable)도 학습했다. 이 응답과 함게 사용될 수 있는 헤더가 Retry-After이다. 이 헤더는 자원을 사용할 수 있는 날짜 혹은 시각을 나타낸다.
- Location
이 헤더는 이전 절에서 언급했듯, 클라이언트에게 자원의 위치를 알려주기 위해 사용된다. 주로 리다이렉션이 발생했을 때나 새로운 자원이 생성되었을 때 사용된다.
- WWW-Authenticate
이 헤더는 상태 코드 401(Unauthorized)와 함께 사용되는 헤더로, 자원에 접근하기 위한 인증 방식을 설명한다. 실제로는 보안 영역$^{realm}$을 함께 알려 주거나 인증에 사용될 문자집합도 알려줄 수 있다.
3.1.3 요청과 응답 모두에서 활용되는 HTTP 헤더
마지막으로 HTTP 요청과 응답 모두에서 공통으로 활용되는 HTTP 헤더들도 살펴보도록 하겠다.
- Date
Date는 메세지가 생성된 날짜와 시각에 관련된 정보를 담은 헤더다.
- Connetction
Connetction 헤더는 클라이언트의 요청과 응답 간의 연결 방식을 설정하는 헤더다. 이 헤더에 명시되는 대표적인 예시로 킵 얼라이브가 있다.
서버나 클라이언트가 연결을 종료하고 싶을 때는 ‘Connection: close’를 통해 알릴 수도 있다.
- Content-length
이 헤더는 본문의 바이트 단위 크기(길이)를 나타낸다.
- Content-Type, Content-Language, Content-Encoding
이 헤더들은 전송하려는 메세지 본문의 표현 방식을 설명하는 헤더다. 그래서 표현 헤더의 일종이라고도 불린다.
Content-Type 헤더는 메세지 본문에서 사용된 미디어 타입을 담고 있다.
Content-Language 헤더는 메세지 본문에 사용된 자연어를 명시한다. 어떤 언어로 작성되었는지 알 수 있는 셈이다. Content-Language의 값은 언어 태그로 명시되며, 언어 태그는 하이픈으로 구분된 구조를 갖는다.
첫 번째 서브 태그는 언어 코드로, 특정 언어를 의미하는 언어 코드가 명시된다. 주로 사용되는 것은 다음과 같다.
언어 | 언어 코드 |
---|---|
한국어 | ko |
영어 | en |
중국어 | zh |
일본어 | ja |
독일어 | de |
프랑스어 | fr |
두 번째 서브 태그는 국가 코드로, 특정 국가를 의미하는 국가 코드가 명시된다.
국가 | 국가 코드 |
---|---|
한국어 | KR |
미국 | US |
영국 | GB |
중국 | CN |
타이완 | TW |
일본 | JP |
독일 | DE |
프랑스 | FR |
두 코드를 조합하면 ‘어떤 국가에서 사용하는 어떤 언어’인지를 알 수 있게 된다.
Content-Encoding 헤더에는 메세지 본문을 압축하거나 변환한 방식이 명시된다. HTTP를 통해 송수신되는 데이터는 전송 속도를 개선하기 위해 종종 압축이나 변환이 이루어지는데, 그럴 때 사용된 방식이 여기에 명시되는 것이다. 대표적인 값으로 ‘gzip’, ‘compress’, ‘deflate’, ‘br’ 등이 있다.
3.2 캐시
캐시는 불필요한 대역폭 낭비와 응답 지연을 방지하기 위해 정보의 사본을 임시로 저장하는 기술이다. 정보의 사본을 임시로 저장하는 것 자체를 ‘캐시한다’, ‘캐싱한다’라고 표현하며 캐시된 데이터를 캐시라고 부른다.
캐시는 웹 브라우저에 저장되기도 하고, 클라이언트와 서버 사이에 위치한 중간 서버에 저장되기도 한다. 전자를 개인 전용 캐시, 후자를 공용 캐시라 부른다. 여기서는 개인 전용 캐시에 초점을 맞춰 이야기 해보겠다.
캐시는 원본을 본따 사본을 저장하기 때문에 원본 데이터가 변경되는 상황에 대비해야 한다.
캐시된 사본 데이터가 얼마나 최신 원본 데이터와 유사한지를 ‘캐시 신선도’라고 표현하기도 한다. 신선도를 유지하는 가장 기본적인 방법은’ 캐시된 데이터에 유효 기간을 설정하는 방법’이다. 그 기간이 만료되었다면 원본 데이터를 다시 요청하는 방식으로 캐시 신선도를 유지할 수 있다.
캐시할 데이터의 유효 기간을 부여하는 방법으로 응답 메세지의 Expires 헤더(날짜)와 Cache-Control 헤더의 Max-Age 값(초)을 사용할 수 있다.
만약 캐시 데이터에 유효 기간을 설정하고 그 기간이 만료되었을 때 만약 원본 데이터가 변하지 않았다면 서버는 굳이 같은 자원을 전송해줄 필요가 없다. 캐시된 자원을 유효 기간을 연장해서 이용하면 된다. 따라서 캐시의 유효 기간이 만료되었다면 클라이언트는 캐시된 자원이 여전히 신선한지 재검사 해야 한다.
캐시의 신선도를 재검사하는 방법은 크게 두 가지 방법이 있다. 하나는 ‘날짜를 기반으로 서버에게 물어보는 방식’이고 다른 것은 ‘엔티티 태그를 기반으로 서버에게 물어보는 방법’이다.
날짜를 기반으로 재검사하는 방식을 먼저 살펴보자. 클라이언트는 If-Modified-Since 헤더를 통해 서버에서 특정 시점 이후로 원본 데이터에 변경이 있었는지 물어볼 수 있다. If-Modified-Since 헤더 값으로 특정 시점(날짜와 시각)이 명시되는데, 이 시점 이후로 원본에 변경이 있었다면 그때만 새 자원으로 응답하도록 서버에게 요청하는 헤더이다.
서버는 If-Modified-Since 헤더가 포함된 요청 메세지를 수신하면 크게 세 가지 상황에 따라 응답을 하게된다.
- 요청받은 자원이 변경되었음
- 요청받은 자원이 변경되지 않았음
- 요청받은 자원이 삭제되었음
첫째, 요청받은 자원이 변경되었다면, 서버는 상태 코드 200과 함께 새로운 자원을 반환한다. 둘째, 요청받은 자원이 변경되지 않았다면, 서버는 메세지 본문이 없는 상태 코드 304(Not Modified)를 통해 클라이언트에게 자원이 변경되지 않았음을 알린다. 셋째, 만약 요청받은 자원이 삭제되었다면, 서버는 상태 코드 404(Not Found)를 통해 요청한 자원이 존재하지 않음을 알린다.
서버는 상태 코드 304를 통한 자원의 ‘변경 여부’뿐만 아니라 자원의 ‘마지막 변경 시점’도 클라이언트에게 알려줄 수 있다. 이를 위한 헤더로 Last-Modified 가 있다. 이 헤더는 특정 자원이 마지막으로 수정된 시점을 나타낸다.
지금까지 날짜를 기반으로 캐시 신선도를 재검사하는 방식을 살펴봤다. 이제 엔티티 태그(이하 Etag)를 사용하는 방법에 대해 알아보자. Etag는 ‘자원의 버전’을 식별하기 위한 정보다. 여기서 버전이란 ‘유의미한 변경 사항’을 의미한다. 즉, 자원이 변경될 때마다 자원의 버전을 식별하는 Etag 값이 변경된다.
클라이언트가 Etag 값이 부여된 자원을 캐시할 때 캐시 신선도를 검사하기 위해 서버에게 ‘이 Etag 값과 일치하는 자원이 있니?’와 같이 물어볼 수 있다. 이를 위해 사용하는 헤더가 바로 If-None-Match이다.
이때도 서버의 자원은 크게 셋 중 하나의 상황을 따른다.
- 요청받은 자원이 변경되었음(Etag 값이 변경됨)
- 요청받은 자원이 변경되지 않았음(Etag 값이 동일함)
- 요청받은 자원이 삭제되었음
첫째로, 요청한 자원이 변경되었다면 Etag 값도 변경되었을 것이다. 이 경우 서버는 상태 코드 200(OK)과 함께 변경된 데이터와 Etag 값을 응답한다. 둘째로, 요청한 자원이 변경되지 않았다면 Etag 값도 변하지 않았을 것이다. 이때 서버는 메세지 본문 없는 상태 코드 304(Not Modified)를 응답한다. 셋째로, 요청한 자원이 삭제되었다면 서버는 상태 코드 404(Not Found)를 응답하게 된다.
3.3 쿠키
HTTP는 기본적으로 상태를 유지하지 않는 스테이트리스 프로토콜이다. 그런데 우리가 사용하는 웹 브라우저에서는 우리가 이전에 체크했던 것들이 다음에 같은 페이지를 방문했을 때 유지되는 경우들을 확인할 수 있다. HTTP 는 스테이트리스함에도 불구하고 이런 기능들은 어떻게 구현되는 것일까?
그런 기능들은 HTTP 쿠키(이하 쿠키)를 통해 구현할 수 있다. 쿠키란 서버에서 생성되어 클라이언트 측에 저장되는 데이터로, 상태를 유지하지 않는 HTTP 특성을 보완하기 위한 수단이다. 이를 이용해 서버가 클라이언트의 상태를 알 수 있게 한다. 쿠키를 이루는 정보는 기본적으로 <이름, 값> 쌍의 형태를 띠고 있고, 추가로 적용 범위와 만료 기간 등 다양한 속성을 가질 수 있다.
서버는 쿠키를 생성해 클라이언트에게 전송하고, 클라이언트는 전달받은 쿠키를 저장해 두었다가 추후 동일한 서버에 보내는 요청 메세지에 쿠키를 포함시켜 전송한다. 서버는 쿠키 정보를 참고해 두 개의 요청이 같은 클라이언트에서 왔는지, 로그인 상태를 유지하고 있는지 등을 알 수 있다.
서버가 생성한 쿠키는 응답 메세지의 Set-Cookie 헤더, 클라이언트 서버로부터 전달받은 쿠키는 요청 메세지의 Cookie 헤더를 통해 전달된다.
쿠키는 브라우저에서 저장되고 관리된다. 크롬 브라우저에서 개발자 도구를 열고, [Application] → [Storage] → [Cookies] 를 확인하면 각 행에 이름- 값 목록들을 확인할 수 있을 것이다. 그리고 이름과 값 외에도 도메인과 경로 등도 있다. www.naver.com 에게 받은 쿠키를 전혀 다른 웹 사이트인 www.google.com 에게 전송하면 안되듯, 쿠키는 사용 가능한 도메인이 정해져 있다. 그래서 응답 메세지 속 Set-Cookie 헤더의 ‘domain’ 속성으로 사용될 곳이 지정된다.
또한 같은 도메인에서 경로별로 쿠키를 구분하여 사용하고 싶을 때가 있을 수 있다. 이럴 때는 “path”로 쿠키가 적용될 경로를 명시하면 된다.
그리고 Expires/Max-age 에 유효 기간을 기입해서 일정 기간이 지나면 삭제되어 전달되지 않도록 한다.
쿠키를 학습할 때 쿠키의 한계도 알아두는 것이 좋다. 쿠키의 대표적인 한계는 바로 보안이다. 이를 보완하기 위한 속성으로 Secure와 HttpOnly라는 속성이 있다. Secure는 HTTPS 프로토콜이 사용되는 경우에만 쿠키를 전송되도록 하는 속성이다.
HttpOnly는 HTTP 송수신을 통해서만 쿠키를 이용하도록 제한하는 속성이다. 이를 통해 쿠키 관련 데이터가 자바 스크립트로 접근 가능하지 않도록 함으로써 해커가 가로채거나 위변조하지 못하게 막을 수 있다.
3.4 콘텐츠 협상과 표현
사이트에 접속할 때 지역이나 계정에 따라 페이지를 구성하는 언어가 달라지는 것은 어떻게 이루어지는 것일가?
이는 HTTP의 콘텐츠 협상을 통해 이루어진다. 콘텐츠 협상이란, 같은 URI에 대해 가장 적합한 ‘자원의 형태’를 제공하는 메커니즘을 의미한다. 같은 URI로 식별 가능한 HTML 문서라 해도, 영어로 요청하면 영어로 된 형태로 제공하고, 한국어로 요청하면 한국어로 된 형태를 제공하는 것이다. 이때, ‘송수신 가능한 자원의 형태’를 자원의 표현이라고 한다. 즉, 콘텐츠 협상은 클라이언트에게 가장 적합한 자원의 표현을 제공하는 메커니즘을 의미한다.
표현이 무엇인지 알게되었으니, GET 메서드를 좀 더 엄밀하게 정의할 수 있다. GET 메서드는 ‘자원의 특정 표현을 습득하기 위한 메서드’라고도 정의할 수 있다.
자원에 대한 다양한 표현 중에서 클라이언트가 선호하는 표현을 반영하고자 콘텐츠 협상 관련 HTTP 헤더들이 사용된다. 주요 헤더로는 선호하는 미디어 타입을 나타내기 위한 Accept 헤더, 선호하는 언어를 나타내기 위한 Accept-Language 헤더, 선호하는 문자 인코딩과 압축 방식을 나타내기 위한 Accept-Charset 및 Accept-Encoding 헤더 등이 있다.
콘텐츠 협상에서 중요한 점은 선호도에 우선순위를 반영할 수 있다는 점이다. 예를 들어 클라이언트가 ‘언어는 한국어를 가장 선호하지만, 영어도 받을 용의가 있다’라는 식으로 여러 선호도를 담은 요청 메세지를 보낼 수도 있다.
이러한 우선순위는 콘텐츠 협상 관련 헤더의 q 값으로 표현된다. q는 Quality Value의 약자로, 특정 표현을 얼마나 선호하는지를 나타내는 값이다. 생략되었을 경우에는 1을 의미하고, 범위는 0부터 1까지이며, 값이 클수록 우선순위가 높다.