Development/Web

HTTP에 관해서

반응형

HTTP

HTTP 는 HyperText Transfer Protocol 의 약자로, 오늘날의 인터넷에서 주로 사용하는 데이터를 송/수신하기 위한 프로토콜이다. 최초의 HTTP를 이용한 데이터 송수신은GET 방식의 HTML 을 위주로 이루어졌으나(문서를 표현하기 위해서) 이는 추후에 여러 메소드 및 미디어 타입이 추가됨에 따라 확장을 했다.

HTTP는 프로토콜이기 때문에 최초 버전에서부터 현재까지의 유의미한 버전이 존재한다.

근데 놀랍게도 대부분의 프로토콜이 큰 변화를 겪는 반면에, HTTP의 경우는 버저닝이 많지가 않아서 크게 세 가지 정도로 히스토리를 정리할 수 있다.

  • HTTP 1.0
  • HTTP 1.1
  • HTTP 2.0

HTTP/1.0

HTTP는 원래 0.9v 부터 시작되었다고 하지만, 사실상 1.0버전이 상용화되어 1996년부터 사용되기 시작했다.

위에 언급했다시피, 최초는 HTML 문서를 송/수신하기 위한 GET Method 와 HTML 파일을 위주로 통신이 이루어졌다.

HTTP_RequestMessage


Request Header부분과 이를 new line으로 개행한 뒤 body를 보내는 것이 HTTP 1.0의 프로토콜이다.(1.1 또한 같다)

HTTP/1.1

HTTP 1.1 같은 경우는 HTTP 1.0의 단순한 구조를 확장하는 방식으로 설계가 되었는데, 현재 사용되는 HTTP Method 및 각종 미디어 타입에 대한 지원 등이 이때 추가가 되게 된다.

RFC 7230, HTTP/1.1: Message Syntax and Routing

  • URL 을 나타내는 방법과 이를 송/수신하는 방법에 대한 스펙을 정의
  • Connection 을 관리하는 방법과 이를 파이프라이닝 하는 방법에 대한 내용
  • Compress 에 관련된 내용(gzip, deflate, compress) … 와 관련된 내용

RFC 7231, HTTP/1.1: Semantics and Content

  • HTTP Method 정의가 추가되었다(GET, POST , PUT, DELETE …)
  • HTTP Status Code 에 대한 정의가 추가되었다(2xx, 3xx, 4xx, 5xx)
  • Content의 타입의 확장을 고려한 Accept, Charset 등의 Negotiation과 관련된 헤더가 추가되었다.
  • 이 밖에도 많은 헤더와 스펙에 관련된 내용이 다수 있는데, HTTP/1.1의 주된 변경점이 이 RFC문서에 담겨있다.

RFC 7232, HTTP/1.1: Conditional Requests

  • Last-ModifiedETag 에 관련된 내용이다.
  • 어떻게보면 RFC 7234의 캐싱부분과 비슷한데, 브라우저가 Last-Modified 의 시간과 서버로 요청하는 If-Modified-Since Header의 시간을 비교해서 데이터가 새로 갱신이 되지 않았을 경우 304 Not Modified 를 return하며 데이터를 전송하지 않고 이전에 캐싱해뒀던 데이터를 꺼내주게 된다.
  • ETag 는 정적 컨텐츠의 변경 여부를 판별할 수 있는 값을 나타내어 Last-Modified 처럼 컨텐츠를 캐싱할 때 사용하도록 스펙을 정의했다. If-None-Match 헤더와 같이 사용하며, 정적 컨텐츠를 요청하는 응답에 ETag 를 내려주고, 이를 가지고 정적 컨텐츠가 변경이 있었는지의 여부를 판단하여 304 Not Modified 를 사용한다.
  • NginxApache 같은 웹서버에서는 이를 자동으로 지원하여, 정적 컨텐츠(css, js, img …) 에 대해서는 ETag를 자동으로 달아주는 기능을 제공하기도 한다.

RFC 7233, HTTP/1.1: Range Requests

  • 206 Partial Content 와 관련된 내용으로 ,큰 데이터를 요청할 때 한번에 전송하는게 아닌 여러 Chunk단위로 쪼개서
  • 데이터를 송/수신하는 방법에 대한 스펙을 정의했다.

RFC 7234, HTTP/1.1: Caching

  • HTTP의 캐싱에 대한 스펙이 추가가 되었다.
  • 현재 사용하고 있는 Web server(Apache, Nginx …) 의 서버들 혹은 어플리케이션에서 직접 제공하는 캐싱기능에 대한 내용이다.
    • 주로 Cache-Control: max-age=<seconds> 와 같은 Header를 추가해서 얼마만큼 캐싱을 할 것인지를 정해서 사용을 한다

RFC 7235, *HTTP/1.1: Authentication

img

  • HTTP Endpoint의 인증을 위한 Basic Authentication 방식이 추가가 되었고, 실제 이를 기반으로 여러 인증수단이 발전되어 현재 사용중이다(OAuth, OpenID, API-KEY Authentication …)

  • 큰 개념은 요청 Header에 인증정보를 넣어 유효한 요청인지를 확인하는 방법을 제공하는 것이다.

  • 혹은, 아래와 같이 username과 password를 넣어서 endpoint를 호출하는 방식도 존재한다.

    https://username:password@www.example.com/

위와 같은 여러 스펙의 추가로 인해서, HTTP/1.0 과는 현저히 많은 데이터를 표현하고 송/수신하는데 문제가 없을 정도로 최초 설계를 잘 하였고, 반면에 대부분의 프로토콜은 많은 변경으로 점차 안정적으로 변해가는데, 추후 확장을 고려한 스펙을 미리 설계를 해놓은 것은 지금에 와서 봐도 납득할만한 스펙이라고 생각이 되었다.

이 뒤에 HTTP/1.2 등이 나오지 않고 바로 HTTP/2.0으로 넘어가게 되는것을 봐도 그 뒤로 특별한 무언가가 추가되지 않아도 될 만큼의 프로토콜인 듯 하다.

 

HTTP/2.0(Google 설명자료)

HTTP/2.0은 비교적 최근에 나온 프로토콜 규약으로, 2015년에 발표가 되었다. (2012년도부터 제안이 있었다고 한다 이는 Google이 개발한 SPDY 라는 프로토콜을 기준으로 높은 성능의 HTTP로 개선을 하기 위한 제안이라고 보여진다)

HTTP/1.1만큼의 파워풀한 스펙은 없지만, 오늘날의 많은 데이터를 처리하기 위한 효율적인 데이터 파이프라이닝높은 처리율 을 위주로 설계가 된 것 같다.

주요 문제점들은 비효율적인 데이터 송/수신에 있다.

기술 자체에 문제가 있는것은 아니지만, 데이터가 필요할때마다 매번 서버로의 Connection을 요청하고 Response를 기다려서 받아야하는 단점이 존재했고 실제로 이와 같은 스펙으로 인하여 주고 받는 데이터가 많아지면 많아질 수록 중복되는 Header와 Blocking 때문에 성능상의 단점이 많이 부각되어 왔었다.

HTTP/2.0에서는 이러한 데이터를 주고 받는 부분에서의 비효율적인 부분을 개선하기 위해 등장했다.

주요 개념으로는 Stream, Message, Frame 이 있다.

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

Server Push

HTTP/2.0 Server Push

HTTP/1.1에서는 단방향(Request -> Response) 로의 데이터 송/수신이 이루어졌다면, HTTP/2.0에서는 위와 같이 서버에서 데이터를 보낼 수 있는 Server Push 기능이 추가가 되었다.

PUSH_PROMISE 프레임을 통해 서버에서 클라이언트로 데이터를 보내겠다는 PROMISE를 한 뒤, 이를 Response가 보내지기 전에 먼저 보낸다.

클라이언트는 이를 수신한 뒤에 어떤 리소스가 올 지를 예측할 수 있고, 해당 리소스가 오게 되면

Prioritize

HTTP/2.0 Prioritize

위와 같은 Server Push에서만 봐도, 프레임의 순서는 매우 중요하다. Response가 가기 전에 먼저 보내야 할 프레임(PUSH_PROMISE)이 있기도 하고 나중에 보내도 될 프레임(DATA FRAME)이 존재하기도 하기 때문에 모든 송수신 데이터에 대해서 우선순위를 지정해서 통신을 하게 된다. 이는 위의 예 말고도, 여러 데이터에 대한 가중치를 계산을 하여 순서를 정하게 된다.

Pipelining, HOL(Head of Line) Blocking 문제 해결

  • HTTP/1.1의 경우는 데이터를 얻기 위해서는 이전의 데이터가 다 전송이 되어야 다음 데이터를 전송하는 구조로 되어 있다. Keep-Alive 를 사용하면 1.1을 사용해도 데이터를 파이프라이닝을 할 수 있지만, HTTP/2.0 스펙에서는 서버와의 연결이 된 한 Stream에서는 모든 요청이 pipelining되어 처리된다.

  • 아래 sequence에서 보다시피 데이터를 송/수신하는데 걸리는 시간이 많이 줄어들게 되며, 이전 데이터를 받는걸 기다리는 대기시간(Head Of Line) 이 없어지게 된다.

    HTTP HOL Blocking

Multiplexing

Http/2.0 Multiplexing

Multiplexing의 경우는 서버와의 연결을 맺은 뒤, HTTP 메세지를 프레임단위로 쪼개서 송/수신하는 방식으로 원래는 HTTP/1.1에서 여러 Connection을 맺은 뒤 데이터를 주고 받아야 하는데, pipelining 과 같이 한 채널을 통해 데이터를 쪼개서 보내는 방식을 의미한다(사실 pipelining과 multiplexing의 차이에 대해서는 잘 모르겠다.)

Compression

HTTP/2.0 Compression

HTTP/1.1의 비효율적인 부분 중 하나는 중복적인 헤더로 인한 송수신 데이터의 크기가 크다는 것에 있다.

아무래도 똑같은 리소스를 주고 받는데도 같은 데이터를 계속해서 보내고 받는것은 비효율적일 수 밖에 없다.

HTTP/2.0에서는 위와 같이, 이미 받은 데이터에 대해서는 implicit 하게 데이터를 받은 것으로 간주하여, 새로운 프레임에 대한 데이터만 비교하여 받게 된다. 이를 통해 전송되는 헤더의 크기가 85% ~ 88% 정도 감소했다고 한다(… 엄청나다)

 

Performance

성능 얘기를 안할 수 없는데, HTTP/1.1에다가 Keep-Alive 를 통해 파이프라이닝을 해서 HTTP/2.0과 비교를 하게 되면 성능이 드라마틱하게 차이가 나지는 않는다.

HTTP/1.1을 쌩으로 사용하여 HTTP/2.0과 비교를 했을 경우는.. 엄청난 성능차이를 보인다.

http1-waterfall

위는 HTTP/1.1을 사용했을 때의 웹 페이지 로딩 결과이다.

보면 알겠지만, 어떤 데이터를 받기 위해서는 이전에 보낸 데이터의 전송이 끝난 경우에만 데이터를 보내게 된다(간혹 같이 전송이 시작된 경우도 있는데, 이는 HTTP/1.1의 Pipelining을 이용해 비슷한 류의 데이터끼리 묶어서 전송하기 때문이다)

http2-waterfall

HTTP/2.0의 경우에는 위와같이 데이터가 병렬로 동시에 송수신이 진행이 된다.

이러한 차이로 성능상에서는 HTTP/2.0이 더 많은 요청을 동시에 처리할 수 있는 여건이 된다.

다만 무조건 HTTP/2.0을 쓰면 빨라지고 좋아진다.. 는 아니다

실 서비스에서 Tomcat9에 HTTP/2.0을 적용해서 테스트를 해본 결과 열 수 있는 최대 Connection이 어느정도 정해져있고, Stream을 열 때 마다 많은 메모리를 소모하여 무조건 많은 부하가 들어온다고 해서 이를 만병통치약처럼 해결을 해주지는 않는다. 적절한 튜닝과 설정값의 변경으로 서비스 적용을 하는 것이 좋아보인다.

여담으로, HTTP 에 HTTP/2.0를 사용을 하면 h2c 라는 프로토콜로 통신을 하게 되고, HTTPS 에 HTTP/2.0을 사용하게 되면 h2 라는 프로토콜로 통신을 하게 된다.

직접 테스트를 해보려면


https://imagekit.io/demo/HTTP2-vs-HTTP1 를 방문해서 이미지가 로드되는 속도를 비교해보도록 하자. HTTP/2.0의 경우에 더 빠른 속도를 보이는 것을 알 수 있다.

반응형