본문 바로가기

Frontend

[Frontend_Roadmap] HTTP에 대해 알아야 할 모든 사실

오늘은 인터넷이 어떻게 작동하는지 배운대에 이어서 HTTP에 대해 작성한 블로그을 기반으로 공부합시다.

 

https://cs.fyi/guide/http-in-depth

 

Everything you need to know about HTTP

HTTP is the protocol that every web developer should know, as it powers the entire web. Knowing HTTP can certainly help you develop better applications. In this article, I will discuss what HTTP is, how it came to be, where it stands today, and how we got

cs.fyi

 

HTTP란 무엇입니까?

HTTP는 클라이언트와 서버가 서로 통신하는 방법을 표준화하는 TCP/IP 기반 애플리케이션 계층 통신 프로토콜입니다. 인터넷을 통해 콘텐츠가 요청되고 전송되는 방식을 정의합니다.

 

애플리케이션 계층 프로토콜이란 단순히 호스트(클라이언트 및 서버)가 통신하는 방식을 표준화하는 추상화 계층임을 의미합니다. 또한 HTTP는 클라이언트와 서버 간의 요청과 응답을 얻기 위해 TCP/IP에 의존합니다. 기본적으로 TCP 포트는 80이 사용되지만 다른 포트도 사용할 수 있습니다. 하지만 HTTP는 443 포트를 사용합니다.

 

HTTP/0.9 - The One Liner (1991)

HTTP의 첫번째 문서화된 버전이 HTTP/0.9입니다. 가장 간단한 프로토콜로 GET 이라는 단일 메서드만 존재 했습니다. 클라이언트가 서버의 일부 페이지에 접근해야 한다면 아래와 같은 간단한 요청을 수행했습니다.

GET /index.html

 

그리고 서버의 응답은 아래와 같습니다.

(response body)
(connection closed)

 

즉, 서버는 요청을 받고 응답으로 HTML로 응답하며 콘텐츠가 전송되자마자 연결이 닫혔습니다. HTTP/0.9는 다음과 같은 특징을 갖습니다.

  • Header 없음
  • 'GET' 메서드만 존재
  • 응답은 HTML만 가능

HTTP/1.0 - 1996

HTML 응답용으로만 사용하던 이전 버전과는 달리 HTTP/1.0은 다른 응답 형식(ex: 이미지, 비디오 파일, 일반 텍스트 등)도 처리할 수 있게 되었습니다. 또한 더 많은 메서드(ex: POST, HEAD) 추가, 요청/응답 형식 변경, 요청과 응답 모두 HTTP 헤더 추가, 응답 식별을 위한 상태 코드 추가, 문자 세트 지원 도입, 다중 부분 유형, 인증, 캐싱, 콘텐츠 인코딩 등이 추가되었습니다.

GET / HTTP/1.0
Host: cs.fyi
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5)
Accept: */*

 

클라이언트는 요청과 함께 개인 정보, 필수 응답 유형 등도 보낼 수 있습니다. 요청에 대한 응답은 아래와 같습니다.

HTTP/1.0 200 OK 
Content-Type: text/plain
Content-Length: 137582
Expires: Thu, 05 Dec 1997 16:00:00 GMT
Last-Modified: Wed, 5 August 1996 15:55:28 GMT
Server: Apache 0.84

(response body)
(connection closed)

 

응답의 맨 처음에는 HTTP/1.0 버전이 있고, 상태 코드 200과 이유 문구 설명이 있습니다. 요청 및 응답 헤더가 여전히 ASCII 인코딩으로 유지되었지만 응답 본문은 이미지, 비디오, HTML, 일반 텍스트 등 모든 유형이 될 수 있습니다. 이제 해당 서버는 모든 콘텐츠 유형을 클라이언트에 보낼 수 있습니다.

 

HTTP/1.0의 주요 단점 중 하나는 연결당 여러 요청을 가질 수 없다는 것입니다. 즉, 클라이언트가 서버에 무언가 요청을 할 때마다 새로운 TCP 연결을 열어야 하며 해당 단일 요청이 끝난 후에는 연결이 닫힙니다. 이것이 단점인 이유는 예시를 통해 알아보겠습니다. 

 

10개의 이미지, 5개의 스타일시트, 5개의 자바스크립트 파일이 있는 웹 페이지 방문 했다고 가정해봅시다. 해당 웹 페이지에 대한 요청이 이루어질 때 가져와야 하는 총 20개의 항목이 있습니다. 항목 하나당 개별 연결이 있게 되며 각 항목은 별도의 연결에서 하나씩 제공됩니다. 각 연결에서는 3-Way-Handshake 이라는 방식 때문에 새로운 TCP 연결이 필요하기 때문에 심각한 성능 저하를 초래합니다.

 

3-Way-Handshake

모든 TCP 연결은 애플리케이션 데이터 공유를 시작하기 전에 클라이언트와 서버가 일련의 패킷을 공유하는 3-Way-Handshake로 시작합니다.

  • SYN - 클라이언트가 임의의 숫자(x)를 선택하여 서버로 보냅니다.
  • SYN ACK - 서버는 임의의 숫자로 구성된 ACK 패킷을 클라이언트에 다시 보냄으로써 요청을 승인합니다. 서버가 y와 숫자 x+1을 선택했다고 가정해봅시다.
  • ACK - 클라이언트는 서버로부터 받은 숫자 y를 증가시키고 숫자 y+1이 포함된 ACK 패킷을 다시 보냅니다.

3-Way-Handshake가 완료되면 클라이언트와 서버 간의 데이터 공유가 시작될 수 있습니다. 클라이언트는 마지막 ACK 패킷을 발송하자마자 애플리케이션 데이터 전송을 시작할 수 있지만, 서버는 요청을 이행하기 위해 ACK 패킷이 수신될 때 까지 기다려야 합니다.

3-Way-Handshake

 

연결이 이어지지 않고 요청과 응답이 끝나면 닫히는 단점 이외에도 HTTP는 상태 비저장 프로토콜입니다. 즉, 서버는 클라이언트에 대한 정보를 유지하지 않으므로 각 요청에는 서버가 자체적으로 요청을 응답하는데 필요한 정보가 있어야 합니다. 이는 서버에 부하를 증가시키는 원인이 됩니다.

 

이러한 방식을 요청할 때마다 TCP 연결을 통해 하였으니 얼마나 귀찮은가..!

 

HTTP/1.1 - 1997

HTTP/1.1의 주요 개선사항은 다음과 같습니다. 도입은 1999년에 도입되었다.

  • PUT, PATCH, OPTIONS, DELETE를 도입한 새로운 HTTP 메서드 추가
  • HTTP/1.0의 호스트 이름 식별 헤더가 필수가 아니었지만 HTTP/1.1에서는 필수로 만들었습니다.
  • 지속적인 연결을 위해 연결은 기본적으로 닫히지 않고 열린 상태로 유지되어 여러 요청을 순차적으로 처리했습니다. 연결을 닫으려면 요청에서 Connection: close 헤더를 사용해야만 가능했습니다. 클라이언트가 연결을 안전하게 닫기 위해 마지막 요청에 이 헤더를 보냅니다.
  • 클라이언트가 동일한 연결에서 서버의 응답을 기다리지 않고 서버에 여러 요청을 보낼 수 있고 서버는 요청이 수신된 것과 동일한 순서로 응답을 보내야하는 파이프라이닝을 도입했습니다. 하지만 클라이언트는 첫번째 응답을 다운로드 하고 다음 응답에 대한 콘텐츠가 시작되는 지점을 어떻게 알 수 있을까? 이 문제를 해결하기 위해 클라이언트가 응답이 끝나는 위치를 식별하고 다음 응답을 기다릴 수 있도록 Content-Length 헤더가 있어야 합니다.

Content-Length 헤더를 사용하여 전송이 완료되는 시기를 클라이언트에게 알리고 다음 응답의 시점을 식별할 수 있다. 하지만 이 방식에도 여전히 문제가 있다. 데이터가 동적이고 서버가 콘텐츠의 길이를 미리 알 수 없다면 어떻게 될까요? 이 경우 지속적인 연결 방식의 이점을 누릴 수 없습니다. 이 문제를 해결하기 위해 chunk 인코딩을 도입했습니다.

 

Content-Length: 수신자에게 전송되는 메세지 본문의 크기

 

  • chunk 전송 동적 콘텐츠의 경우 전송이 시작될 때 서버가 Content-Length를 찾을 수 없다면 콘텐츠를 chunk(조각)별로 전송하기 시작하고 전송 시 각 chunk에 대해 Content-Length를 추가할 수 있습니다. 그리고 모든 chunk가 전송되어 전체 전송이 완료되면 전송이 완료된 클라이언트를 식별하기 위해 빈 chunk, 즉 Content-Length가 0으로 설정된 chunk를 보냅니다. 클라이언트에 chunk 전송을 알리기 위해서 서버는 Transfer-Encoding: Chunked 헤더를 포함합니다.
  • 기본 인증만 있었던 HTTP/1.0과 달리 다이제스트 및 프록시 인증이 추가

SPDY - 2009

Google에서 대기 시간을 줄이면서 웹 속도를 높이고 보안까지 향상시키기 위해 대체 프로토콜인 SPDY를 발표했습니다.

SPDY는 상표이며 약어가 아니다.

 

대역폭을 늘리면 초기에는 네트워크 성능이 좋아지지만, 이후에 성능 향상이 크게 차이나지 않는 시점이 오게됩니다. 하지만 대기 시간에 대해 동일한 작업을 수행하면 즉, 대기 시간을 계속 낮추면 성능이 지속적으로 향상되는 것을 확인했습니다.

 

- 대역폭(Bandwidth): 초당 전송되는 데이터 양

- 대기 시간(Latency): 소스와 대상 사이에서 데이터가 이동하는데 걸리는 시간

 

하지만 구글에서는 새로운 표준을 만들기를 원하지 않았기에 HTTP/2를 탄생시키고 SPDY를 더 이상 사용하지 않는 동시에 이를 HTTP에 병합하기로 결정했습니다.

 

HTTP/2 - 2015

HTTP/2는 대기 시간이 짧은 콘텐츠 전송을 위해 설계되었습니다. HTTP/1.1 이전 버전과의 주요 기능이나 차이점은 다음과 같습니다.

  • 텍스트 대신 바이너리
  • 멀티 플렉싱 - 단일 연결을 통한 다중 비동기 HTTP 요청
  • HPACK을 사용한 헤더 압축
  • 서버 푸시 - 단일 요청에 대한 다중 응답
  • 요청 우선 순위
  • 보안

HTTP/2

 

1. 바이너리 프로토콜

HTTP/2는 바이너리 프로토콜을 이용하여 대기 시간을 줄였습니다. 다만 바이너리이기 때문에 구문 분석은 더 쉽지만 사람 눈으로 읽을 수는 없습니다. HTTP/2의 주요 구성 요소는 프레임과 스트림입니다.

 

프레임과 스트림

이제 HTTP 메세지는 하나 이상의 프레임으로 구성됩니다. 메타데이터를 위한 HEADERS 프레임과 페이로드를 위한 DATA 프레임이 있으며, HTTP/2 스펙을 통해 확인할 수 있는 여러 유형의 프레임 등이 있습니다.

 

모든 HTTP/2 요청 및 응답에는 고유한 스트림 ID가 부여되며 이는 프레임으로 구분됩니다. 프레임은 바이너리 데이터 조각일 뿐입니다. 프레임 모음을 스트림이라고 합니다. 각 프레임에는 해당 프레임이 속한 스트림을 식별하는 스트림 ID가 있으며 각 프레임에는 공통 헤더가 있습니다. 또한 클라이언트가 시작한 모든 요청은 스트림 ID를 홀수로 사용하고 서버의 응답에는 짝수로 사용합니다.

 

RST_STREAM은 일부 스트림을 중단하는 데 사용되는 특수 프레임입니다. 즉, 클라이언트는 이 스트림이 필요하지 않음을 서버에 알리기 위해 RST_STREAM 프레임을 보낼 수 있습니다. HTTP/1.1에서 서버가 클라이언트에 대한 응답을 중지하는 방법은 연결을 닫는 것이 유일했고 이후 요청을 위해서는 새 연결을 열어야 했기 때문에 대기 시간이 증가했습니다. HTTP/2에 있는 동안 클라이언트는 RST_STREAM을 사용하여 연결이 계속 열려 있고 다른 스트림이 계속 재생되는 동안 특정 스트림 수신을 중지할 수 있습니다.

 

2. 다중화

HTTP/2는 이제 바이너리 프로토콜이고 요청과 응답에 프레임과 스트림을 사용하므로 일단 TCP 연결이 열리면 추가 연결을 하지 않고 동일한 연결을 통해 모든 스트림이 비동기적으로 전송됩니다. 서버는 동일한 비동기 방식을오 응답합니다. 즉, 응답에는 순서가 없으며 클라이언트는 할당된 스트림 ID를 사용하여 특정 패킷이 속한 스트림을 식별합니다. 클라이언트는 시간이 걸리는 요청을 기다릴 필요가 없으며 다른 요청은 계속 처리됩니다.

 

3. 헤더 압축

동일한 클라이언트에서 서버에 지속적으로 액세스할 때 헤더를 통해 중복되어 전송되는 데이터가 많이 있고 때로는 쿠키가 헤더 크기를 늘려 대역폭 사용량을 증가시킬 수 있다는 점에서 헤더 압축의 필요성이 대두되었다.

 

요청 및 응답과 달리 헤더는 gzip 또는 압축 등의 형식으로 압축되지 않지만 헤더 압축을 위한 다른 메커니즘이 있다. 메커니즘에 대해서는 넘어가자. 여튼 이 메커니즘을 통해 서버는 후속 요청에서 반복적인 헤더(ex: user-agent 등)를 생략하고 헤더 테이블을 사용하여 이를 참조합니다.

 

4. 서버 푸시

서버 푸시는 클라이언트가 특정 리소스를 요청할 것임을 서버가 알고 클라이언트가 요청하지 않고도 클라이언트에 이를 푸시할 수 있는 HTTP/2의 또 다른 놀라운 기능입니다. 예를 들어 브라우저가 웹 페이지를 로드하고 전체 페이지를 구문 분석하여 서버에서 로드해야 하는 원격 콘텐츠를 찾은 다음 해당 콘텐츠를 가져오기 위해 서버에 후속 요청을 보낸다고 가정해 보겠습니다.

 

서버 푸시를 사용하면 서버는 클라이언트가 요구할 것으로 알고 있는 데이터를 푸시하여 왕복 횟수를 줄일 수 있습니다. 수행 방법은 서버가 PUSH_PROMISE라는 특수 프레임을 보내 클라이언트에게 "이 리소스를 보내려고 합니다!"라고 알리는 것입니다. PUSH_PROMISE 프레임은 푸시를 발생시킨 스트림과 연결되어 있으며 약속된 스트림 ID, 즉 서버가 푸시할 리소스를 보내는 스트림을 포함합니다.

 

5. 우선순위 요청

클라이언트는 스트림이 열리는 HEADERS 프레임에 우선순위 정보를 포함시켜 스트림에 우선순위를 할당할 수 있습니다. 언제든지 PRIORITY 프레임을 보내 스트림의 우선순위를 변경할 수 있습니다.

 

우선순위 정보가 없으면 서버는 요청을 순서없이 처리합니다. 스트림에 우선순위가 할당된 경우 서버는 이 우선순위 정보를 기반으로 어떤 요청으로 처리하기 위해 얼마나 많은 리소스를 제공해야 하는지 결정합니다.

 

6. 보안

HTTP/2에 TLS를 통한 보안을 필수로 적용해야 하는지 여부에 대한 광범위한 논의가 있었습니다만 결국 의무화 하지는 않기로 결정됐다. 하지만 대부분의 공급업체는 TLS를 통해 사용될 경우에만 HTTP/2를 지원할 것이라고 밝혔습니다. 즉, 거의 필수로 TLS를 통해 암호화를 해야한다.