HTTP/2 소개

HTTP/2는 애플리케이션 내에서 이전에 수행했던 많은 HTTP/1.1 해결 방법을 실행 취소하고 전송 계층 자체 내에서 이러한 문제를 해결할 수 있도록 하여 애플리케이션을 더 빠르고 단순하며 강력하게 만듭니다(드문 조합). 게다가 애플리케이션을 최적화하고 성능을 개선할 수 있는 완전히 새로운 기회가 많이 주어집니다.

HTTP/2의 주요 목표는 전체 요청 및 응답 다중화를 사용 설정하여 지연 시간을 줄이고, HTTP 헤더 필드의 효율적인 압축을 통해 프로토콜 오버헤드를 최소화하며, 요청 우선순위 지정 및 서버 푸시에 대한 지원을 추가하는 것입니다. 이러한 요구사항을 구현하기 위해 새로운 흐름 제어, 오류 처리, 업그레이드 메커니즘과 같은 기타 프로토콜 개선사항이 크게 지원되고 있지만, 이는 모든 웹 개발자가 이해하고 애플리케이션에서 활용해야 하는 가장 중요한 기능입니다.

HTTP/2는 어떤 방식으로도 HTTP의 애플리케이션 의미 체계를 수정하지 않습니다. HTTP 메서드, 상태 코드, URI, 헤더 필드와 같은 모든 핵심 개념은 그대로 유지됩니다. 대신 HTTP/2는 클라이언트와 서버 간에 데이터의 형식 지정 (프레임) 및 전송 방식을 수정하고 두 서버 모두 전체 프로세스를 관리하고 애플리케이션의 모든 복잡성을 새 프레이밍 레이어로 숨깁니다. 따라서 기존의 모든 애플리케이션을 수정 없이 제공할 수 있습니다.

HTTP/1.2는 왜 안 되나요?

HTTP Working Group에서 설정한 성능 목표를 달성하기 위해 HTTP/2에서는 이전 HTTP/1.x 서버 및 클라이언트와 하위 호환되지 않는 새로운 바이너리 프레이밍 레이어를 도입하므로 주 프로토콜 버전이 HTTP/2로 증가합니다.

하지만 원시 TCP 소켓을 사용하여 웹 서버 (또는 커스텀 클라이언트)를 구현하지 않는 한 차이가 없습니다. 모든 새로운 하위 수준 프레이밍은 클라이언트와 서버에서 자동으로 수행됩니다. 눈에 띄는 유일한 차이점은 성능이 향상되고 요청 우선순위 지정, 흐름 제어, 서버 푸시와 같은 새로운 기능의 가용성입니다.

SPDY 및 HTTP/2의 간략한 역사

SPDY는 Google에서 개발하여 2009년 중반에 발표한 실험 프로토콜로, HTTP/1.1의 잘 알려진 성능 제한을 일부 해결하여 웹페이지의 로드 지연 시간을 줄이는 것을 주요 목표로 삼았습니다. 구체적으로 프로젝트 목표를 다음과 같이 설정했습니다.

  • 페이지 로드 시간 (PLT)을 50% 줄입니다.
  • 웹사이트 작성자가 콘텐츠를 변경할 필요가 없도록 합니다.
  • 설치의 복잡성을 최소화하고 네트워크 인프라 변경을 방지할 수 있습니다.
  • 오픈 소스 커뮤니티와의 협력을 통해 이 새로운 프로토콜을 개발합니다.
  • 실험 프로토콜의 유효성을 검증하기 위해 실제 성능 데이터를 수집합니다.

최초 발표 후 Google의 소프트웨어 엔지니어인 Mike Belshe와 Roberto Peon은 새로운 SPDY 프로토콜의 실험적 구현에 관한 첫 번째 결과, 문서 및 소스 코드를 공유했습니다.

지금까지는 실험실 조건에서만 SPDY를 테스트했습니다. 초기 결과는 매우 고무적입니다. 시뮬레이션된 홈 네트워크 연결을 통해 상위 25개 웹사이트를 다운로드하면 성능이 크게 향상되어 페이지가 최대 55% 더 빠르게 로드됩니다. (Chromium 블로그)

2012년에 이르러 새로운 실험용 프로토콜은 Chrome, Firefox 및 Opera에서 지원되었으며 Google, Twitter, Facebook과 같은 대형 사이트 및 소형 사이트의 수가 급격히 늘어나면서 인프라 내에 SPDY를 배포했습니다. 실제로 SPDY는 업계의 채택률이 높아짐에 따라 사실상의 표준이 되었습니다.

이러한 트렌드를 관찰한 HTTP Working Group (HTTP-WG)은 SPDY로부터 얻은 교훈을 얻어 이를 바탕으로 빌드하고 개선하며 공식 'HTTP/2' 표준을 제공하려는 새로운 노력을 시작했습니다. 새로운 헌장의 초안이 작성되고 HTTP/2 제안에 대한 공개 요청이 있었으며 실무 그룹 내에서 많은 논의를 거친 후 SPDY 사양이 새로운 HTTP/2 프로토콜의 출발점으로 채택되었습니다.

그 후 몇 년 동안 SPDY와 HTTP/2는 동시에 함께 발전해 왔으며, SPDY는 HTTP/2 표준의 새로운 기능과 제안을 테스트하는 데 사용된 실험용 브랜치로 기능했습니다. 이론적으로 좋아 보이는 것이 실제로는 효과가 없을 수 있고 그 반대의 경우도 마찬가지입니다. SPDY는 각 제안을 HTTP/2 표준에 포함하기 전에 테스트하고 평가하는 방법을 제공했습니다. 결국 이 프로세스는 3년이 걸렸으며 10여 개의 중간 초안이 작성되었습니다.

  • 2012년 3월: HTTP/2 제안 요청
  • 2012년 11월: HTTP/2의 초안 (SPDY 기반)
  • 2014년 8월: HTTP/2 초안-17 및 HPACK 초안-12 게시
  • 2014년 8월: Working Group에서 HTTP/2에 대한 최종 요청
  • 2015년 2월: IESG가 HTTP/2 및 HPACK 초안을 승인
  • 2015년 5월: RFC 7540 (HTTP/2) 및 RFC 7541 (HPACK) 발행

2015년 초에 IESG는 새로운 HTTP/2 표준을 검토하고 게시하도록 승인했습니다. 그 직후 Chrome팀은 TLS용 SPDY 및 NPN 확장 프로그램의 지원 중단 일정을 발표했습니다.

HTTP/1.1에서 HTTP/2의 주요 변경사항은 향상된 성능에 중점을 둡니다. 다중화, 헤더 압축, 우선순위 지정 및 프로토콜 협상과 같은 일부 주요 기능은 이전에 공개되었지만 SPDY라는 비표준 프로토콜에서 이루어진 작업에서 발전했습니다. Chrome은 Chrome 6부터 SPDY를 지원했지만 대부분의 이점이 HTTP/2에 포함되어 있으므로 이제 SPDY를 지원하지 않습니다. Google은 2016년 초에 SPDY 지원을 중단하고, 동시에 Chrome에서도 ALPN으로 대체하기 위해 NPN이라는 TLS 확장 프로그램에 대한 지원을 중단할 계획입니다. 서버 개발자는 HTTP/2 및 ALPN으로 전환하는 것이 좋습니다.

Google은 HTTP/2를 주도한 개방형 표준 프로세스에 기여하게 된 것을 기쁘게 생각하며, 업계의 폭넓은 표준화 및 구현을 고려할 때 HTTP/2가 널리 채택되기를 바랍니다. (Chromium 블로그)

SPDY 및 HTTP/2가 함께 발전해온 덕분에 서버, 브라우저, 사이트 개발자들은 새로운 프로토콜이 개발됨에 따라 실제적인 경험을 얻을 수 있었습니다. 결과적으로 HTTP/2 표준은 가장 광범위하게 테스트를 거친 최고의 표준 중 하나입니다. HTTP/2가 IESG에서 승인될 무렵에는 철저하게 테스트되고 프로덕션에 즉시 사용 가능한 수십 개의 클라이언트 및 서버 구현이 있었습니다. 실제로 최종 프로토콜이 승인된 지 불과 몇 주 만에 여러 인기 브라우저 (및 많은 사이트)에서 완전한 HTTP/2 지원을 배포함에 따라 많은 사용자가 이미 프로토콜의 이점을 누리고 있었습니다.

설계 및 기술 목표

HTTP 프로토콜의 이전 버전은 구현의 단순성을 위해 의도적으로 설계되었습니다. HTTP/0.9는 월드 와이드 웹을 부트스트랩하기 위한 한 줄 프로토콜이었습니다. HTTP/1.0에서는 정보 표준으로 HTTP/0.9의 인기 있는 확장 프로그램을 문서화했으며, HTTP/1.1에서는 공식 IETF 표준을 도입했습니다. HTTP의 간략한 역사를 참조하세요. 따라서 HTTP/0.9-1.x는 의도대로 정확하게 전달되었습니다. HTTP는 인터넷에서 가장 널리 사용되는 애플리케이션 프로토콜 중 하나입니다.

하지만 구현 단순성에는 애플리케이션 성능이 저하되기도 합니다. HTTP/1.x 클라이언트는 동시 실행을 달성하고 지연 시간을 줄이기 위해 여러 연결을 사용해야 합니다. HTTP/1.x는 요청 및 응답 헤더를 압축하지 않아 불필요한 네트워크 트래픽을 유발합니다. HTTP/1.x는 리소스 우선순위를 효과적으로 지정할 수 없으므로 기본 TCP 연결을 제대로 사용하지 못하게 됩니다.

이러한 한계는 치명적이지는 않지만, 일상 생활에서 웹 애플리케이션의 범위, 복잡성 및 중요성이 계속 증가하면서 웹 개발자와 사용자 모두에게 점점 더 큰 부담을 주게 되었으며, 이는 HTTP/2가 해결하고자 하는 바로 그 격차입니다.

HTTP/2를 사용하면 헤더 필드 압축을 도입하고 동일한 연결에서 여러 동시 교환을 허용하여 네트워크 리소스를 보다 효율적으로 사용하고 지연 시간을 줄일 수 있습니다. 특히 동일한 연결에서 요청 및 응답 메시지를 인터리브 처리할 수 있으며 HTTP 헤더 필드에 효율적인 코딩을 사용합니다. 또한 요청의 우선순위를 지정할 수 있으므로 더 중요한 요청이 더 빨리 완료되어 성능이 더욱 개선됩니다.

결과적으로 프로토콜은 HTTP/1.x에 비해 사용할 수 있는 TCP 연결 수가 더 적기 때문에 네트워크에 더 친화적입니다. 즉, 다른 흐름과의 경쟁이 줄어들고 연결 수명이 길어지므로 사용 가능한 네트워크 용량을 더 잘 활용할 수 있습니다. 마지막으로, HTTP/2에서는 바이너리 메시지 프레이밍을 사용하여 메시지를 보다 효율적으로 처리할 수도 있습니다. (Hypertext Transfer Protocol 버전 2, 초안 17)

HTTP/2는 이전의 HTTP 표준을 대체하는 것이 아니라 확장한다는 점에 유의해야 합니다. HTTP의 애플리케이션 의미 체계는 동일하며 HTTP 메서드, 상태 코드, URI, 헤더 필드와 같은 제공되는 기능이나 핵심 개념은 변경되지 않습니다. 이러한 변경은 HTTP/2의 범위를 명시적으로 벗어났습니다. 즉, 고수준 API는 동일하게 유지되지만 하위 수준의 변경사항이 이전 프로토콜의 성능 제한을 어떻게 해결하는지 이해하는 것이 중요합니다. 바이너리 프레이밍 레이어와 기능을 간단히 살펴보겠습니다

바이너리 프레이밍 레이어

HTTP/2의 모든 성능 향상 중 핵심은 HTTP 메시지가 캡슐화되어 클라이언트와 서버 간에 전송되는 방식을 지정하는 새로운 바이너리 프레이밍 계층입니다.

HTTP/2 바이너리 프레이밍 레이어

'계층'은 애플리케이션에 노출되는 소켓 인터페이스와 상위 HTTP API 사이에 최적화된 새로운 인코딩 메커니즘을 도입하기 위한 디자인 선택을 나타냅니다. HTTP 의미 체계(예: 동사, 메서드, 헤더)는 영향을 받지 않지만 전송 중에 인코딩되는 방식은 다릅니다. 줄바꿈으로 구분된 일반 텍스트 HTTP/1.x 프로토콜과 달리 모든 HTTP/2 통신은 더 작은 메시지와 프레임으로 분할되며 각각 바이너리 형식으로 인코딩됩니다.

결과적으로 클라이언트와 서버는 서로를 이해하기 위해 새로운 바이너리 인코딩 메커니즘을 사용해야 합니다. 즉, HTTP/1.x 클라이언트는 HTTP/2 전용 서버를 이해하지 못하며 그 반대의 경우도 마찬가지입니다. 다행히 클라이언트와 서버가 필요한 모든 프레이밍 작업을 대신 수행하기 때문에 애플리케이션은 이러한 모든 변경사항을 인식하지 않아도 됩니다.

스트림, 메시지, 프레임

새로운 바이너리 프레이밍 메커니즘이 도입되면 클라이언트와 서버 간에 데이터가 교환되는 방식이 변경됩니다. 이 프로세스를 설명하기 위해 HTTP/2 용어를 익혀 보겠습니다.

  • 스트림: 설정된 연결 내의 양방향 바이트 흐름으로, 하나 이상의 메시지를 전달할 수 있습니다.
  • 메시지: 논리적 요청 또는 응답 메시지에 매핑되는 프레임의 전체 시퀀스입니다.
  • Frame: HTTP/2에서 최소 통신 단위이며 각각 프레임 헤더를 포함하며, 최소한 프레임이 속한 스트림을 식별합니다.

이러한 용어의 관계는 다음과 같이 요약할 수 있습니다.

  • 모든 통신은 단일 TCP 연결을 통해 수행되며 양방향 스트림을 여러 개 전달할 수 있습니다.
  • 각 스트림에는 양방향 메시지 전달에 사용되는 고유 식별자와 우선순위 정보(선택사항)가 있습니다.
  • 각 메시지는 하나 이상의 프레임으로 구성된 요청 또는 응답과 같은 논리적 HTTP 메시지입니다.
  • 프레임은 특정 유형의 데이터를 전달하는 통신의 최소 단위입니다(예: HTTP 헤더, 메시지 페이로드 등 다른 스트림의 프레임을 인터리빙한 다음 각 프레임의 헤더에 삽입된 스트림 식별자를 통해 다시 조합할 수 있습니다.

HTTP/2 스트림, 메시지, 프레임

간단히 말해 HTTP/2는 HTTP 프로토콜 통신을 바이너리 인코딩 프레임의 교환으로 분할합니다. 그런 다음 이 프레임은 특정 스트림에 속하는 메시지에 매핑되며, 모든 프레임은 단일 TCP 연결 내에서 다중화됩니다. HTTP/2 프로토콜에서 제공하는 다른 모든 기능과 성능 최적화를 지원하는 기반입니다.

요청 및 응답 다중화

HTTP/1.x에서 성능 개선을 위해 클라이언트가 여러 동시 요청을 수행하려는 경우 여러 TCP 연결을 사용해야 합니다 (여러 TCP 연결 사용 참조). 이는 연결별로 한 번에 하나의 응답만 전달되도록 하는 (응답 큐) HTTP/1.x 제공 모델의 직접적인 결과입니다. 더 안 좋은 점은 HOL(Head-of-Line) 차단과 기본 TCP 연결의 비효율적인 사용으로 이어집니다.

HTTP/2의 새로운 바이너리 프레이밍 레이어는 이러한 제한을 없애고 클라이언트와 서버가 HTTP 메시지를 독립된 프레임으로 분할하여 인터리브한 후 다른 쪽 끝에서 다시 조립할 수 있도록 하여 전체 요청 및 응답 다중화를 지원합니다.

공유 연결 내 HTTP/2 요청 및 응답 다중화

이 스냅샷은 동일한 연결 내에서 진행 중인 여러 스트림을 캡처합니다. 클라이언트는 DATA 프레임 (스트림 5)을 서버로 전송하고, 서버는 스트림 1과 3의 인터리브 처리된 프레임 시퀀스를 클라이언트로 전송합니다. 따라서 세 개의 동시 스트림이 진행 중입니다.

HTTP 메시지를 독립 프레임으로 분할하여 인터리브한 후 다른 쪽 끝에서 다시 조합하는 기능은 HTTP/2의 가장 중요한 개선사항입니다. 사실, 모든 웹 기술의 전체 스택에 걸쳐 수많은 성능 이점이 미치는 파급 효과를 도입하여 다음과 같은 이점을 제공할 수 있게 되었습니다.

  • 여러 요청을 하나도 차단하지 않고 병렬로 인터리브 처리합니다.
  • 여러 응답을 하나도 차단하지 않고 병렬로 인터리빙할 수 있습니다.
  • 단일 연결을 사용하여 여러 요청과 응답을 동시에 전달합니다.
  • 불필요한 HTTP/1.x 해결 방법을 삭제합니다 (연결된 파일, 이미지 스프라이트, 도메인 샤딩과 같은 HTTP/1.x 최적화 참조).
  • 불필요한 지연 시간을 제거하고 가용한 네트워크 용량의 활용도를 개선하여 페이지 로드 시간을 줄입니다.
  • 기타...

HTTP/2의 새로운 바이너리 프레이밍 레이어는 HTTP/1.x에서 발견되는 대기 행렬 막힘 문제를 해결하며, 요청 및 응답의 병렬 처리 및 전송을 사용 설정하기 위해 여러 연결이 필요하지 않습니다. 그 결과 애플리케이션을 더 빠르고 간단하며 저렴하게 배포할 수 있습니다.

스트림 우선순위 지정

HTTP 메시지를 여러 개별 프레임으로 분할할 수 있고 여러 스트림의 프레임을 멀티플렉싱할 수 있게 되면 클라이언트와 서버 모두에서 프레임이 인터리빙되어 전달되는 순서가 중요한 성능 고려사항이 됩니다. 이를 용이하게 하기 위해 HTTP/2 표준에서는 각 스트림이 연관된 가중치와 종속 항목을 갖도록 허용합니다.

  • 각 스트림에는 1에서 256 사이의 정수 가중치가 할당될 수 있습니다.
  • 각 스트림에는 다른 스트림에 대한 명시적 종속 항목이 부여될 수 있습니다.

스트림 종속 항목과 가중치의 조합을 통해 클라이언트는 선호하는 응답 수신 방식을 표현하는 '우선순위 지정 트리'를 구성하고 통신할 수 있습니다. 결과적으로 서버는 이 정보를 사용하여 CPU, 메모리 및 기타 리소스의 할당을 제어하여 스트림 처리의 우선순위를 지정할 수 있으며, 응답 데이터를 사용할 수 있게 되면 대역폭을 할당하여 우선순위가 높은 응답을 클라이언트에 최적으로 전달할 수 있습니다.

HTTP/2 스트림 종속 항목 및 가중치

HTTP/2 내에서 스트림 종속 항목은 다른 스트림의 고유 식별자를 상위 요소로 참조함으로써 선언됩니다. 식별자가 생략되면 스트림이 '루트 스트림'에 종속됩니다. 스트림 종속 항목을 선언하는 것은 가능하면 상위 스트림에 종속 항목보다 먼저 리소스를 할당해야 함을 나타냅니다. 즉, '응답 C보다 먼저 응답 D를 처리하고 전달하세요.'입니다.

동일한 상위 요소 (즉, 동위 스트림)를 공유하는 스트림에는 가중치에 비례하여 리소스가 할당되어야 합니다. 예를 들어 스트림 A의 가중치가 12이고 동위 스트림 B의 가중치가 4인 경우, 이러한 각 스트림이 수신해야 하는 리소스 비율을 결정하는 방법은 다음과 같습니다.

  1. 모든 가중치 합계: 4 + 12 = 16
  2. 각 스트림 가중치를 총 가중치(A = 12/16, B = 4/16)로 나눕니다.

따라서 스트림 A는 가용 리소스의 3/4을 수신하고 스트림 B는 1/4을 수신해야 하며, 스트림 B는 스트림 A에 할당된 리소스의 1/3을 수신해야 합니다. 위 이미지에서 몇 가지 실무형 예를 더 살펴보겠습니다 왼쪽에서 오른쪽으로:

  1. 스트림 A와 스트림 B는 모두 상위 종속 항목을 지정하지 않으며 암시적 '루트 스트림'에 종속된다고 말합니다. 스트림 A의 가중치는 12이고 B의 가중치는 4입니다. 따라서 비례 가중치에 따라 스트림 B는 스트림 A에 할당된 리소스의 1/3을 수신해야 합니다.
  2. 스트림 D는 루트 스트림에 종속되고 C는 D에 종속됩니다. 따라서 D는 C보다 먼저 전체 리소스를 할당받아야 합니다. C의 종속 항목이 더 높은 선호도를 전달하므로 가중치는 중요하지 않습니다.
  3. 스트림 D는 C보다 먼저 전체 리소스를 할당해야 합니다. C는 A와 B보다 먼저 전체 리소스를 할당받아야 합니다. 스트림 B는 스트림 A에 할당된 리소스의 1/3을 수신해야 합니다.
  4. 스트림 D는 E와 C보다 먼저 전체 리소스를 할당받아야 하고, E와 C는 A 및 B보다 먼저 동일한 리소스를 할당받아야 하며, A와 B는 가중치에 따라 비례하여 리소스를 할당받아야 합니다.

위의 예에서 볼 수 있듯이 스트림 종속 항목과 가중치의 조합은 리소스 우선순위 지정을 위한 표현적 언어를 제공합니다. 이는 종속 항목과 가중치가 다양한 여러 리소스 유형이 있는 탐색 성능을 개선하는 데 중요한 기능입니다. 또한 HTTP/2 프로토콜을 사용하면 클라이언트가 언제든지 이러한 환경설정을 업데이트할 수 있으므로 브라우저를 더욱 최적화할 수 있습니다. 즉, 사용자 상호작용 및 기타 신호에 응답하여 종속 항목을 변경하고 가중치를 재할당할 수 있습니다.

출처당 연결 1개

새로운 바이너리 프레이밍 메커니즘이 적용되면 HTTP/2에서 스트림을 병렬로 다중화하기 위해 더 이상 여러 TCP 연결이 필요하지 않습니다. 각 스트림은 인터리브 처리되고 우선순위를 지정할 수 있는 여러 프레임으로 분할됩니다. 따라서 모든 HTTP/2 연결이 영구적이고 출처당 하나의 연결만 필요하므로 성능상의 수많은 이점이 있습니다.

SPDY와 HTTP/2 모두의 핵심 기능은 단일 완전 정체 제어 채널에서 임의 멀티플렉싱하는 것입니다. 이 기능이 얼마나 중요하고 잘 작동하는지는 정말 놀랍습니다. 제가 좋아하는 측정항목 중 하나는 단일 HTTP 트랜잭션만 전달하는 생성된 연결의 비율입니다. 따라서 해당 트랜잭션이 모든 오버헤드를 부담합니다. HTTP/1의 경우 활성 연결의 74% 가 단일 트랜잭션만 전달합니다. 영구 연결은 원하는 만큼 유용하지 않습니다. 하지만 HTTP/2에서는 이 수치가 25%로 떨어집니다. 이는 오버헤드 감소 측면에서 큰 이점입니다. (HTTP/2는 Firefox, Patrick McManus에서 사용 가능)

대부분의 HTTP 전송은 짧고 버스트가 많은 반면 TCP는 수명이 긴 일괄 데이터 전송에 최적화되어 있습니다. HTTP/2는 동일한 연결을 재사용하여 각 TCP 연결을 보다 효율적으로 사용하고 전체 프로토콜 오버헤드를 크게 줄일 수 있습니다. 또한 연결을 적게 사용하면 전체 연결 경로(즉, 클라이언트, 중개자, 원본 서버)에서 메모리와 처리 공간이 줄어듭니다. 따라서 전체 운영 비용이 절감되고 네트워크 사용률 및 용량이 개선됩니다. 따라서 HTTP/2로 전환하면 네트워크 지연 시간이 줄어들 뿐만 아니라 처리량이 개선되고 운영 비용이 절감됩니다.

흐름 제어

흐름 제어는 발신자가 원하지 않거나 처리할 수 없는 데이터로 인해 수신자에게 과도한 부담을 주지 않도록 하는 메커니즘입니다. 수신자가 사용 중이거나, 과부하 상태이거나, 특정 스트림에 일정량의 리소스만 할당하려고 할 수도 있습니다. 예를 들어 클라이언트가 높은 우선순위로 대용량 동영상 스트림을 요청했지만 사용자가 동영상을 일시중지했으며 클라이언트는 이제 불필요한 데이터를 가져오고 버퍼링하는 것을 방지하기 위해 서버에서 동영상 전송을 일시중지하거나 제한하려고 합니다. 또는 프록시 서버는 다운스트림이 빠르고 업스트림 연결이 느릴 수 있으며, 이와 유사하게 다운스트림이 데이터를 업스트림 속도와 일치하도록 얼마나 빠르게 데이터를 전송하는지 제어하여 리소스 사용량을 제어하려고 합니다.

위의 요구사항을 보면 TCP 흐름 제어가 생각나시나요? 이는 문제가 사실상 동일하기 때문입니다 (흐름 관리 참고). 하지만 HTTP/2 스트림은 단일 TCP 연결 내에서 다중화되므로 TCP 흐름 제어가 충분히 세분화되지 않으며 개별 스트림의 전송을 규제하는 데 필요한 애플리케이션 수준 API를 제공하지 않습니다. 이 문제를 해결하기 위해 HTTP/2에서는 클라이언트와 서버가 스트림 및 연결 수준에서의 흐름 제어를 구현할 수 있게 해주는 간단한 빌딩 블록 세트를 제공합니다.

  • 흐름 제어는 방향성을 갖습니다. 각 수신기는 각 스트림과 전체 연결에 원하는 창 크기를 설정하도록 선택할 수 있습니다.
  • 흐름 제어는 크레딧 기반입니다. 각 수신기는 초기 연결 및 스트림 흐름 제어 창 (바이트 단위)을 알립니다. 이 창은 발신자가 DATA 프레임을 내보낼 때마다 감소하고 수신기에서 전송된 WINDOW_UPDATE 프레임을 통해 증가합니다.
  • 흐름 제어를 사용 중지할 수 없습니다. HTTP/2 연결이 설정되면 클라이언트와 서버가 SETTINGS 프레임을 교환하며, 이 프레임은 양방향으로 흐름 제어 창 크기를 설정합니다. 흐름 제어 창의 기본값은 65,535바이트로 설정되지만, 수신자는 데이터가 수신될 때마다 WINDOW_UPDATE 프레임을 전송하여 큰 최대 창 크기(2^31-1바이트)를 설정하고 유지할 수 있습니다.
  • 흐름 제어는 엔드 투 엔드가 아니라 홉별입니다. 즉, 중개자가 자체적인 기준과 휴리스틱을 기반으로 리소스 사용을 제어하고 리소스 할당 메커니즘을 구현할 수 있습니다.

HTTP/2는 흐름 제어를 구현하기 위한 특정 알고리즘을 지정하지 않습니다. 대신 간단한 기본 요소를 제공하고 클라이언트와 서버에 구현을 미루며, 클라이언트와 서버에서는 이를 사용하여 리소스 사용 및 할당을 규제하는 맞춤 전략을 구현할 뿐 아니라 웹 애플리케이션의 실제 성능과 인지된 성능 (속도, 성능 및 인간 인식 참고)을 모두 개선하는 데 도움이 되는 새로운 전송 기능을 구현할 수 있습니다.

예를 들어 애플리케이션 레이어 흐름 제어를 사용하면 브라우저가 특정 리소스의 일부만 가져오고 스트림 흐름 제어 창을 0으로 줄여서 가져오기를 보류한 다음 나중에 재개할 수 있습니다. 즉, 브라우저가 이미지의 미리보기나 첫 번째 스캔을 가져와 표시하고 우선순위가 높은 다른 가져오기가 계속 진행되도록 허용하고, 더 많은 중요한 리소스의 로드가 완료되면 가져오기를 재개할 수 있습니다.

서버 푸시

HTTP/2의 또 다른 강력한 새 기능은 서버가 단일 클라이언트 요청에 대해 여러 응답을 보낼 수 있다는 것입니다. 다시 말해 서버는 원래 요청에 대한 응답 외에도 클라이언트가 명시적으로 요청하지 않아도 서버는 클라이언트로 추가 리소스를 푸시할 수 있습니다 (그림 12~5).

서버에서 푸시 리소스에 대해 새 스트림 (프로미스) 시작

브라우저에 이러한 메커니즘이 필요한 이유는 무엇인가요? 일반적인 웹 애플리케이션은 수십 개의 리소스로 구성되며, 이러한 모든 리소스는 서버에서 제공하는 문서를 검사하여 클라이언트가 검색합니다. 따라서 추가 지연 시간을 없애고 서버가 관련 리소스를 미리 푸시하도록 하면 어떨까요? 서버는 클라이언트에 어떤 리소스가 필요한지 이미 알고 있습니다. 이것이 서버 푸시입니다.

실제로 데이터 URI (리소스 인라인 처리 참고)를 통해 CSS, JavaScript 또는 기타 애셋을 인라인 처리한 적이 있다면 이미 서버 푸시를 사용한 경험이 있습니다. 리소스를 문서에 직접 인라인 처리함으로써 클라이언트가 요청할 때까지 기다리지 않고 해당 리소스를 클라이언트로 푸시합니다. HTTP/2를 사용하면 동일한 결과를 얻을 수 있지만 추가적인 성능 이점이 있습니다. 푸시 리소스는 다음과 같을 수 있습니다.

  • 클라이언트에 의해 캐시됨
  • 여러 페이지에서 재사용됨
  • 다른 리소스와 함께 다중화
  • 서버에서 우선순위 지정
  • 클라이언트에 의해 거부됨

PUSH_PROMISE 시작하기

모든 서버 푸시 스트림은 PUSH_PROMISE 프레임을 통해 시작되며, 이 프레임은 설명된 리소스를 클라이언트에 푸시하라는 신호를 서버의 인텐트에 알립니다. 이 프레임은 푸시된 리소스를 요청하는 응답 데이터보다 먼저 전달되어야 합니다. 이 전송 순서는 매우 중요합니다. 이러한 리소스에 대한 중복 요청이 생성되지 않도록 클라이언트는 서버에서 푸시할 리소스를 알아야 합니다. 이 요구사항을 충족하는 가장 간단한 방법은 약속한 리소스의 HTTP 헤더만 포함하는 모든 PUSH_PROMISE 프레임을 상위 요소의 응답 (즉, DATA 프레임)보다 먼저 전송하는 것입니다.

클라이언트가 PUSH_PROMISE 프레임을 수신한 후 원하는 경우 RST_STREAM 프레임을 통해 스트림을 거부할 수 있습니다. 예를 들어 리소스가 이미 캐시에 있기 때문에 이러한 상황이 발생할 수 있습니다. 이는 HTTP/1.x에 비해 중요한 개선사항입니다. 반면에 리소스 인라인 처리 사용(HTTP/1.x의 일반적인 '최적화' 사용)은 '강제 푸시'와 동일합니다. 클라이언트는 인라인 처리된 리소스를 개별적으로 선택 해제하거나 취소하거나 처리할 수 없습니다.

HTTP/2에서도 클라이언트가 서버 푸시의 사용 방식을 완전히 제어합니다. 클라이언트는 동시에 푸시되는 스트림 수를 제한할 수 있습니다. 스트림을 처음 열 때 푸시되는 데이터의 양을 제어하도록 초기 흐름 제어 창을 조정하거나 서버 푸시를 완전히 사용 중지할 수 있습니다. 이러한 환경설정은 HTTP/2 연결 시작 시 SETTINGS 프레임을 통해 전달되며 언제든지 업데이트될 수 있습니다.

푸시된 각 리소스는 인라인된 리소스와는 달리 클라이언트에 의해 개별적으로 다중화되고, 우선순위가 지정되고, 처리될 수 있는 스트림입니다. 브라우저에서 적용하는 유일한 보안 제한사항은 푸시된 리소스가 동일 출처 정책을 준수해야 한다는 것입니다. 즉, 서버는 제공된 콘텐츠에 대해 권한이 있어야 합니다.

헤더 압축

각 HTTP 전송에는 전송된 리소스와 그 속성을 설명하는 헤더 집합이 포함되어 있습니다. HTTP/1.x에서 이 메타데이터는 항상 일반 텍스트로 전송되고 전송당 500~800바이트의 오버헤드가 추가되고 HTTP 쿠키를 사용할 경우 킬로바이트 이상의 오버헤드가 추가됩니다. (프로토콜 오버헤드 측정 및 제어를 참조하세요.) 이러한 오버헤드를 줄이고 성능을 개선하기 위해 HTTP/2는 두 가지 간단하지만 강력한 기법을 사용하는 HPACK 압축 형식을 사용하여 요청 및 응답 헤더 메타데이터를 압축합니다.

  1. 전송되는 헤더 필드를 정적 Huffman 코드로 인코딩할 수 있어 개별 전송 크기가 줄어듭니다.
  2. 클라이언트와 서버는 모두 이전에 본 헤더 필드의 색인 생성된 목록을 유지관리하고 업데이트해야 합니다 (즉, 공유 압축 컨텍스트를 설정함). 그런 다음 이전에 전송된 값을 효율적으로 인코딩하기 위한 참조로 사용합니다.

Huffman 코딩을 사용하면 전송 시 개별 값을 압축할 수 있으며 이전에 전송된 값의 색인이 생성된 목록을 사용하면 전체 헤더 키와 값을 효율적으로 조회하고 재구성하는 데 사용할 수 있는 색인 값을 전송하여 중복 값을 인코딩할 수 있습니다.

HPACK: HTTP/2용 헤더 압축

한 가지 추가 최적화로 HPACK 압축 컨텍스트는 정적 테이블과 동적 테이블로 구성됩니다.정적 테이블은 사양에 정의되어 있으며 모든 연결에 사용할 수 있는 공통 HTTP 헤더 필드 목록 (예: 유효한 헤더 이름)을 제공합니다. 동적 테이블은 처음에는 비어 있으며 특정 연결 내에서 교환된 값에 따라 업데이트됩니다. 따라서 이전에 본 적 없는 값에는 정적 Huffman 코딩을 사용하고 정적 테이블이나 동적 테이블에 이미 있는 값을 색인을 대체하여 각 요청의 크기를 줄일 수 있습니다.

HPACK의 보안 및 성능

초기 버전의 HTTP/2 및 SPDY는 커스텀 사전과 함께 zlib을 사용하여 모든 HTTP 헤더를 압축했습니다. 이를 통해 전송되는 헤더 데이터의 크기가 85% ~88% 감소하고 페이지 로드 시간 지연 시간이 크게 개선되었습니다.

업로드 링크가 375Kbps에 불과한 저대역폭 DSL 링크에서는 특히 요청 헤더 압축을 통해 특정 사이트 (즉, 많은 수의 리소스 요청을 발행한 사이트)의 페이지 로드 시간이 크게 개선되었습니다. 헤더 압축으로 인해 페이지 로드 시간이 45~1142ms 단축된 것으로 확인되었습니다. (SPDY 백서, chromium.org)

그러나 2012년 여름, TLS 및 SPDY 압축 알고리즘을 대상으로 한 'CRIME' 보안 공격이 발표되었으며, 이로 인해 세션 도용이 발생할 수 있었습니다. 결과적으로 zlib 압축 알고리즘은 발견된 보안 문제를 해결하고, 효율적이고 간편하게 올바르게 구현하며, HTTP 헤더 메타데이터를 제대로 압축할 수 있도록 특별히 설계된 HPACK으로 대체되었습니다.

HPACK 압축 알고리즘에 관한 자세한 내용은 IETF HPACK - HTTP/2의 헤더 압축을 참고하세요.

추가 자료