프로그래밍

[Backend-RoadMap] HTTP에 대한 모든 것 - (2)

손가든 2024. 8. 1. 13:33

지난 포스팅에서는 HTTP의 기초 개념과 초기 버전에 대해서 학습한 뒤 정리해보았다.

 

 

이번 포스팅은 HTTP/1.0 버전보다 개선되어 출시된 1.1버전과 2.0 버전 및 HTTP에 대한 새로운 시도에 대해 알아보며

 

새롭게 확장된 HTTP의 변화를 통해 호스트들에게 어떤 것이 가능해졌는지 공부해 보도록 할 것이다.

 


 

HTTP/1.1

1.0이 출시된지 3년만인 199년에 1.1이 출시되었고, 1.0 버전에 비해 많은 개선이 이루어졌다.

 

주요 개선 사항을 알아보자.

 

  1. PUT, PATCH, OPTIONS, DELETE가 HTTP 메서드에 새롭게 추가된다.

 

 

 2. 필수가 아니었던 호스트 식별 헤더가 필수로 변경된다.

 

 

 3. 한 요청당 하나의 연결이 필요했던 문제를 해결하기 위해 지속적 연결(Persistent Connections)이 도입된다.

 

1.1 버전에서 연결은 기본적으로 닫히지 않고 열린 상태로 유지되어 여러 순차적 요청이 허용된다.

 

연결을 닫으려면 Connection : close 헤더를 요청에 사용하도록 한다.

 

클라이언트는 보통 마지막 요청에서 이 헤더를 보내 연결을 안전하게 닫도록 한다.

 

 

 4. 클라이언트가 동일한 연결에서 서버로부터 응답을 기다리지 않고도 서버에 여러 요청을 보낼 수 있는 파이프라이닝 지원이 도입된다.

 

파이프라이닝은 요청을 응답과 1대1로 대기하며 주고받는 방식이 아닌 비동기로 보내놓고 응답을 받는 형식인데, 이렇게 되면 어디까지가 1개의 응답 데이터인지 알 수 없다.

 

여러 데이터를 연속적으로 응답받아야 하기 때문이다.

 

따라서 지속적 연결(Persistent Connections)의 이점을 유지하면서도 파이프라이닝의 이점을 얻기 위해 응답이 끝나는 지점이 어디인지 식별해줄 Content-Length 헤더를 통해 식별한다.

 

이때, 이 해결 방시은 중요한 문제가 발생한다.

 

데이터의 크기가 동적인 영상과 같은 콘텐츠는 길이를 미리 일 수 없기 때문이다.

 

따라서 이 문제를 해결하기 위해 1.1버전에서는 청크 인코딩을 도입한다.

 

5. 청크 전송 방식이 도입된다.

 

동적 콘텐츠의 경우, 서버가 전송을 시작할 때 Content-Length를 실제로 미리 알 수 없는 경우가 있다.

 

따라서 이 경우에 미리 정해둔 단위 크기의 청크로 잘라 나눠서 전송한다.

 

그리고 모든 청크가 전송되면 즉, 콘텐츠의 전체 전송이 완료되면 호스트가 전송이 완료되었음을 식별하기 위해 빈 청크(Content-Length = 0)을 전송한다.

 

수신 호스트측에 청크 전송을 알리기 위해 송신 호스트는 Transfer-Encoding: chunked 헤더를 포함하게 된다.

 

 6. 기본 인증만 존재하던 1.0 버전을 개선하기 위해 Digest 와 Proxy 인증이 추가된다.

 

Digest 인증은 클라이언트의 신원을 확인하기 위한 보안 메커니즘으로, 클라이언트로부터 해시값을 계산하도록 하여, 자격 증명을 요구하는 방식이다.

 

서버 측과 클라이언트 간의 통신 데이터가 암호화 되어 보안이 강화되었다.

 

 

Proxy 인증은 클라이언트가 서버 대신 프록시 서버로 자격 증명을 인증하도록 하는 메커니즘이다.

 

이는 주 서버 이전에 프록시 서버에서 클라이언트의 요청을 검열 및 통제할 수 있는 장점이 있다.

 

 

HTTP/1.1의 문제점

수년동안 표준이었던 1.1이지만 문제점이 없는 것은 아니었다.

 

웹은 빠르게 변화하면서 1.1 버전은 노후화되기 시작했다.

 

최신 웹 페이지를 로딩하는 것은 예전보다 더 많은 리소스를 사용한다.

 

요즘 간단한 웹 페이지는 30개 이상의 TCP 연결을 수행해야 한다.

 

 

근데 1.1 버전에는 지속적인 연결(Persistent Connections)이 있는데 왜 30개나 열어야 될까?

 

그 이유는 TCP 통신 한개 단위로 보았을 때는 지속적으로 연결을 유지하더라도, 한 요청 후에 응답을 받기 전엔 새로운 요청을 할 수 없기 때문이다.

 

그렇다면 파이프라이닝은 무슨소리인가? 하니, TCP 통신 여러개를 열어 병렬로 요청들을 전송하는 것을 말한다.

 

다시 말하면 한 TCP 통신에서는 응답이 올 때까지 재 요청이 불가능한데, 이를 Head-of-Line Blocking (HOL Blocking)이라고 한다.

 

이로 인해 성능 저하가 발생하고, 따라서 병렬 파이프라이닝을 통해 여러개의 TCP를 열어 요청에 대한 응답을 병렬로 기다리도록 한다.

 

개발자들은 이러한 단점을 극복하기 위해 스프라이트 시트 사용, CSS로 인코딩된 이미지, 하나의 거대한 데이터를 분할하는 기술인 샤딩(Sharding) 등의 해결책을 구현하게 된다.

 

 

SPDY의 등장

구글은 웹을 더 빠르게 만들고 지연 시간을 줄이면서 보안을 개선하기 위한 대체 프로토콜을 실험하기 위해 SPDY를 발표했다.

 

처음 SPDY가 나왔을 때, HTTP의 대체제가 아닌 보완제로 출시되어 어플리케이션 계층에 존재하고 기존 HTTP 통신의 요청 방식을 수정하는 변환 계층이었다.

 

하지만 이는 사실상 표준이 되게되고, 구글은 HTTP 와 SPDY라는 두개의 경쟁 표준이 생기는 것을 원하지 않아 HTTP에 병합하여 HTTP/2를 탄생시키고 SPDY는 지원을 중단한다.

 

 

HTTP/2

2버전의 HTTP는 SPDY와 같이 콘텐츠의 저 지연 전송을 위해 설계되었다.

 

HTTP/1.1과의 주요 차이점은 아래와 같다.

 

 1. 텍스트 대신 바이너리 프로토콜

바이너리 프로토콜은 지연 시간 증가 문제를 해결하기 위한 방법으로 도입되었다.

 

바이너리 프로토콜이기 때문에 파싱하기 쉽지만, 더 이상 인간의 눈으로 읽을 수 없게 됬다.

 

HTTP/2의 주요 구성 요소는 프레임과 스트림이다.

 

 

프레임 및 스트림

 

HTTP 메시지는 이제 하나 이상의 프레임으로 구성된다.

 

메타 데이터 용인 Headers 프레임과 페이로드용의 Data 프레임이 있으며, 그 외에도 여러 다른 유형의 프레임이 존재한다.

 

모든 HTTP/2 요청과 응답 데이터에는 고유한 스트림 id가 부여되고, 스트림은 프레임이라는 이진 데이터 조각으로 나눠진다.

 

각 프레임은 소속된 스트림의 식별 ID가 있으며, 각 프레임에는 공통 헤더가 있다.

 

스트림 ID는 요청 및 응답 별로 고유한 것 외에, 요청 측은 ID가 홀수이고, 응답 측은 ID가 짝수라는 특징이 있다.

 

 

HEADERS 와 DATA 외에도 특히 특별한 프레임 유형이 있는데 RST_STREAM이다.

 

이는 클라이언트가 서버로 해당 프레임을 전송하면 현재의 STREAM이 더 이상 필요하지 않은 요청이라는 의사를 전달한다.

 

따라서 서버 측에서 응답을 보내는 것을 중단하도록 하는 유일한 방법은 연결을 아예 닫아버리는 것이었는데, 다른 스트림에서 RST_STREAM을 사용하여 특정 스트림 수신을 중단하면서도 TCP 연결을 계속 유지하도록 할 수 있게 되었다.

 

 

 2. 멀티플렉싱 : 단일 연결을 통한 여러 비동기 HTTP 요청

하나의 TCP 연결 내에서 여러개의 스트림을 생성하고, 각 스트림은 독립적인 요청/응답의 단위로 존재하므로 하나의 연결에서 동시에 여러 요청을 주고 받을 수 있다.

 

프레임들은 동일한 TCP 연결을 통해 교차 전송될 수 있고, 수신 측에서는 스트림 식별자를 사용하여 해당 스트림에 속한 프레임들을 재조립하여 원래의 요청이나 응답으로 복원하여 읽어들인다.

 

이는 HOL Blocking 병목 현상을 해결한다.

 

 

 3. 헤더 압축

 

헤더 압축은 전송 헤더를 최적화하는 것을 목표로 한 별도의 RFC(공식적인 인터넷 관련 기술 문서)의 일부이다.

 

핵심은 중복되는 헤더 데이터가 너무 많고, 쿠키가 헤더 크기를 증가시켜 대역폭 사용량과 지연 시간이 증가하는 문제를 해결하기 위함이다.

 

 

 4. 서버 푸시

 

서버 푸시는 HTTP/2의 또 다른 엄청난 기능으로, 서버는 클라이언트가 특정 리소스를 요청할 것이라는 것을 알고 있기 때문에 클라이언트가 요청하지 않아도 클라이언트에 푸시할 수 있다.

 

서버 푸시는 클라이언트가 요구할 것이라는 것을 알고 있는 데이터를 푸시하여 왕복을 줄일 수 있도록 한다.

 

서버 푸시를 수행하기 위해 서버는 PUSH_PROMISE라는 특수 프레임을 보내 클라이언트에게 "너가 필요한 이 리소스 줄테니까 요청 중단해"라고 알린다.

 

 

 5. 요청 우선순위 지정

각 클라이언트는 PRIORITY 프레임을 보내 특정 스트림의 우선순위를 변경할 수 있다.

 

우선순위 정보가 없으면 서버는 요청을 순서 변경 없이 처리하지만, 우선순위가 할당되어 있다면 이 우선순위 정보를 기반으로 순서를 변경한다.

 

 

 6. 보안

 

HTTP/2에 TLS를 통한 보안을 필수로 하지 않았지만, 대부분의 공급업체(웹 브라우저 ex. Google Chrome, 웹 서버 소프트웨어 ex. NginX 등)가 TLS를 통해 사용할 때만 HTTP/2를 지원하기에 거의 필수가 되었다.

 

TLS를 통해 구현된 HTTP/2는 몇가지 요구 사항들이 있는데

TLS 1.2 이상의 버전을 사용해야 하고, 특정 수준의 최소 키 크기가 있어야 하며 임시 키가 필요하다.