HTTP/0.9
- HTTP의 시작은 1989년 팀 버너스 리(Tim Berners-LEE)에 의해 시작되었습니다. 초기 버전인 HTTP/0.9는 매우 단순한 프로토콜로서 GET 동작이 유일했으며 헤더와 상태 코드(status code)도 없었기 때문에 문제가 발생한 경우 특정 html 파일에 오류에 대한 설명과 함께 응답하였습니다.
HTTP/1.0
- HTTP/1.0이 발표되면서 지금의 HTTP의 메시지 구조와 동일한 형태가 만들어졌습니다. HTTP/1.0 부터 시작 라인에는 메서드, PATH, HTTP 버젼 응답일 경우에는 status code가 기재되었습니다. 헤더가 추가되어 다양한 속성을 지정할 수 있었으며, 특히 Content-Type 덕분에 HTML 파일 뿐만 아니라 다른 형식의 파일도 전송할 수 있게 되었습니다.
HTTP/1.0의 문제점
- HTTP 1.0에서부터 야기된 문제점은 하나의 요청과 응답마다 연결 및 종료가 반복되는 것이었습니다. 예를 들어서 웹 페이지를 요청하면 html과 그에 딸린 css, js, 이미지 등의 수많은 각각의 자원들을 요청하고 수신 받기 위해 불필요한 TCP 연결과 종료를 반복 수행합니다.
특히 TCP는 안정적인 송.수신을 위해 연결 시 마다 3-way-handshake 작업이 필요하고, 혼잡 제어 기능으로 인해 Slow Start로 초기 전송 속도가 느리다는 특징이 있기 때문에 HTTP에서 위와 같은 문제점은 상당히 부각될 수 있습니다.
HTTP/1.0+의 keep-alive
- keep-alive는 사용하지 않기로 결정되어 HTTP/1.1 명세에서 빠졌지만, 아직 많은 브라우저와 서버는 이 초기 keep-alive 커넥션을 사용하고 있습니다. 그리고 그 설계상의 문제는 HTTP1/1.1에서 보완하여 '지속 커넥션'으로 재탄생 하였습니다.
HTTP/1.0+ keep-alive 사용법
- 사용 방법으로는 HTTP/1.0 keep-alive 커넥션을 구현한 클라이언트는 커넥션을 유지하기 위해서 요청에 Connection:Keep-alive 헤더를 포함시킵니다. 이 요청을 받은 서버는 그 다음 요청도 이 커넥션을 통해 받고자 한다면, 응답 메시지에 같은 헤더를 포함시켜 응답합니다. 만약, 응답에 Connection:Keep-alive 헤더가 없으면 클라이언트는 서버가 keep-alive를 지원하지 않으며, 응답 메시지가 전송되고 나면 서버가 커넥션을 끊을 것이라 추정합니다.
HTTP/1.0+ keep-alive 옵션
- keep-alive 헤더는 커넥션을 유지하기를 바라는 요청일 뿐입니다. 클라이언트나 서버가 keep-alive 요청을 받았다고 해서 무조건 그것을 따를 필요는 없습니다. 언제든지 keep-alive 커넥션을 끊을 수 있으며 keep-alive 커넥션에서 처리되는 트랜잭션의 수를 제한할 수도 있습니다. keep-alive의 동작은 keep-alive 헤더의 쉼표로 구분된 옵션들로 제어할 수 있으며, 이를 통해 지속 시간과 응답의 개수(이대로 동작한다는 보장은 없음)를 지정할 수 있습니다.
keep-alive와 멍청한( dumb ) 프락시
- dumb 프락시는 클라이언트가 요청한 Connection:Keep-Alive 헤더를 이해하지 못하고 그대로 서버에 전달하였을 때 문제가 발생됩니다. 클라이언트와 서버는 Keep-Alive를 유지하지만 Dumb 프록시는 이를 이해하지 못하고 잘못된 처리를 하거나 연결을 끊어버리기 때문에 클라이언트 입장에서는 다시 연결하여 요청을 보내야 하고, 서버 입장에서는 클라이언트와 연결이 끊긴줄 모르기 때문에 연결 정보를 유지하는데 리소스를 낭비합니다.
이러한 문제를 해결하는 차선책으로 Proxy-Connection이라는 비표준 헤더를 추가하여 똑똑한 프락시는 이를 Connection:Keep-Alive를 지원하기 때문에 Proxy-Connection:Keep-Alive 에서 Connection:Keep-Alive로 변환하여 서버에 전달합니다. 하지만, 이 또한 똑똑한 프락시 다음에 멍청한 프락시가 올 경우 동일한 문제가 발생됩니다.
HTTP/1.1
- HTTP/1.1 표준은 이전 버전에서 발견된 프로토콜 모호성 및 성능을 개선하기 위해 도입되었습니다. 커넥션과 관련한 기술로는 지속 커넥션과 파이프라이닝 커넥션이 추가되었습니다.
HTTP/1.1의 Chunked Encoding
- Chunked Encoding은 HTTP 메시지 본문을 일정한 크기의 Chunk로 분할하여 전송하는 방식을 의미합니다. 즉, 클라이언트에서 서버 또는 서버에서 클라이언트로 요청 및 응답할 때 사용할 수 있습니다.
- 나누어진 각각의 Chunk는 길이 필드와 Chunk 데이터로 구성됩니다. 길이 필드는 16진수로 표시되어 Chunk 데이터의 바이트 수를 나타내며, 이를 통해 수신측에서 각각의 Chunk를 구분할 수 있으며, 마지막 Chunk의 길이가 0으로 표시합니다. Chunked Encoding 사용하면은 Content-Length를 사용할 필요가 없습니다.
- 이러한 Chunked 방식을 통해서 메시지 요청이 끝나지 않았어도 일부 응답 데이터를 만들어 응답할 수 있습니다. 그리고 너무 큰 응답 데이터에 대해서 Chunked Encoding 방식으로 분할하여 응답할 때도 유용합니다.
HTTP/1.1의 지속 커넥션
- HTTP/1.1에서는 HTTP/1.0+의 keep-alive 커넥션을 지원하지 않는 대신, 설계가 더 개선된 지속 커넥션을 지원합니다. 지속 커넥션은 기본적으로 활성화되어 있으며 트랜잭션이 끝난 다음 커넥션을 끊으려면 Connection:close 헤더를 명시해야 합니다. 그리고 클라이언트와 서버는 언제든지 커넥션을 끊을 수 있으며 Connection:close를 보내지 않는다고 해서 영원히 커넥션을 유지하겠다는 것을 뜻하지 않습니다.
- HTTP/1.1은 HTTP/1.0+의 keep-alive와 달리 기본적으로 활성화 되어 있기 때문에 dumb 프락시가 Connection 헤더를 잘못 이해하여 잘못된 처리를 하거나 연결을 끊어버리는 상황이 상대적으로 적습니다. 하지만, 여전히 dumb 프락시가 지속 커넥션 또한 이해하지 못하는 경우가 있어 잘못된 처리를 할 수 있기 때문에 똑똑한 프락시를 사용하거나 Dumb 프록시에 대한 문제를 해결하는 기능이 내장되어 있는 HTTP/2.0을 사용하는 것이 좋습니다.
파이프라인 커넥션
- HTTP/1.1은 지속 커넥션을 통해서 요청을 파이프라이닝 할 수 있습니다. 이를 통해 클라이언트는 요청을 연속으로 여러 개를 보낼 수 있어 지속 커넥션의 성능을 더 높여줍니다.
HLOB( Head of Line Blocking )
- 파이프라인 커넥션을 통해서 여러개의 요청을 한 번에 보낼 수 있습니다. 하지만, 서버는 요청 순서에 맞게 응답을 처리해야 하기 때문에 첫 번째 요청이 오래 걸리는 요청이라면 두 번째, 세 번째 요청이 그만큼 지연될 수 밖에 없습니다. 이러한 현상을 HOLB( Head of Line Blocking )이라고 합니다. HTTP/2.0에서는 멀티플렉싱 알고리즘으로 대체되었습니다.
Domain Sharding
- 파이프라이닝을 대체하기 위해 차선책으로 나온 기술이며, 브라우저들은 하나의 도메인에 대해 여러 개의 Connection을 생성하여 병렬로 요청을 보내고 받는 방식으로 성능을 개선하였습니다. 예를들어서 한 도메인당 6~13개의 TCP 연결들을 동시 생성해 여러 리소스를 한 번에 다운로드 하며 이를 Domain Sharding이라고 부릅니다.
하지만, Domain의 주소를 찾기 위해 DNS Lookup 과정과 Connection으로 인한 추가적인 작업 및 메모리가 필요하다는 단점이 있습니다. 그렇기 때문에 HTTP/2.0 부터는 멀티 플렉싱을 통해 하나의 TCP 연결에서 여러 개의 요청과 응답을 처리할 수 있기에 사용되지 않습니다.
HTTP/1.1의 헤더 중복
- HTTP/1.1의 헤더에는 많은 메타 정보들을 담을 수 있습니다. 또한 cookie에 대한 활용도도 높아졌기 때문에 매 요청시 마다 전송하려는 값 보다 헤더의 값이 더 큰 경우가 많아졌습니다. 그리고 지속 커넥션 속에서 주고 받는 연속된 요청 데이터가 중복된 헤더값을 가지고 있는 경우가 많아 쓸데없는 네트워크 트래픽량이 증가하게 되었습니다.
HTTP/2.0
- HTTP/2.0은 기존 HTTP/1.1 버전의 성능 향상에 초점을 맞춘 버전입니다. HTTP/1.1까지는 한 번에 하나의 파일만 전송이 가능했습니다. 비록 파이프라이닝 기술이 있었지만, 파이프라이닝은 HOLB의 문제 때문에 잘 활용되지 못했습니다.
- HTTP/2.0은 HTTPS를 사용하는 경우에만 사용될 수 있습니다.
HTTP/2.0 Binary Framing Layer
- HTTP/1.1 이전 버전들은 text 형태로 전송되었던 것과 달리 HTTP/2.0부터는 HTTP 계층 최 하단에 Binary Framing 계층을 두어 Binary Framing 계층에서 메시지를 프레임 단위로 나누고 인코딩하여 Binary Frame 단위로 송.수신합니다. 그리고 헤더와 바디가 Layer로 구분되고, 이로 인해 데이터 파싱 및 전송 속도가 증가하였고 오류 발생 가능성이 줄어들었습니다. 각각의 프레임마다 데이터의 길이를 가지고 있기 때문에 Content-Length 헤더는 HTTP/2.0부터는 사용되지 않습니다.
- 텍스트 기반이 아닌 바이너리로 인코딩 하였을 때 이점을 예로 들자면, 0과 1로 이루어진 이미지 파일을 텍스트로 기반으로 인코딩 한다면은 0은 아스키 코드로 48이고 1은 아스키 코드로 49입니다. 즉, 텍스트 기반으로 통신할 경우 요청과 응답의 데이터 크기가 훨씬 더 커집니다.
stream과 fame 단위
- HTTP/1.1에서는 HTTP 요청과 응답은 Message 단위밖에 없었습니다. 하지만, HTTP/2.0 부터는 Message라는 단위 외에 Stream, Frame 이라는 단위가 추가되었습니다.
- Frame : HTTP/2.0에서는 통신의 최소 단위이며, Header 혹은 Data가 들어있습니다. 프레임은 스트림 ID, Length, Type(헤더, 바디, 데이터 등의 프레임의 타입 등을 의미), Flags(스트림의 종료 등), Payload 등으로 구성되어 있습니다.
- Message : HTTP/1.1과 마찬가지로 요청, 응답의 단위이며 다수의 Frame으로 이루어진 단위입니다. 클라이언트 또는 서버측은 수신받은 여러 프레임을 하나의 메시지로 완성하여 이를 처리할 수 있습니다.
- Stream : 연결된 Connection 내에서 Frame을 주고 받는 하나의 흐름입니다. 하나의 요청 및 응답을 통해 만들어진 바이너리 프레임은 하나의 스트림에 속하여 송.수신됩니다. 이렇게 하나의 요청, 응답에서 만들어진 여러 바이너리 프레임들은 하나의 스트림에 속해야 하는데, 그 이유는 수신측에서 하나의 스트림을 통해 프레임들을 순서대로 받아야 메시지를 만들 수 있기 때문입니다.
- HTTP/2.0은 HTTP 요청을 여러개의 Frame들로 나누고, 이 Frame들이 모여 요청/응답 Message가 되고, Message는 특정 Stream에 속하여 여러개의 Stream은 하나의 Connection에 속하게 되는 구조입니다. 즉, Stream에 속한 Message를 이루는 여러개의 Frame들이 Connection 내에서 병렬적으로 빠른 송.수신 처리를 할 수 있습니다.
Multiplexing
- HTTP/2.0은 Message를 Frame 단위로 쪼갠 후 stream을 이용하여 요청 및 응답을 송.수신합니다. 여기에 Multiplexing을 도입하여 하나의 커넥션으로 여러 개의 스트림을 통해 동시에 병렬적으로 메시지에 대한 여러 Frame을 응답 순서에 상관없이 주고 받을 수 있습니다.
Server Push
- HTTP/2.0은 클라이언트가 요청한 데이터를 분석하여 서버에서 필요할것 같은 리소스에 대한 추가적인 응답을 할 수 있습니다. 예를들어서 클라이언트가 HTML 파일을 요청했을 때 HTML 문서가 링크하고 있는 CSS, JS 파일을 미리 서버에서 응답할 수 있습니다.
Stream Prioritization
- 하나의 Connection에 여러 요청과 응답이 뒤섞여 버리면 응답을 받았을 때 종속된 프레임이 도착하지 못하는 상황이 발생될 수 있습니다. 이러한 상황을 예방하기 위해서 스트림의 1~256까지 우선순위를 두어 우선순위 높은 스트림을 보다 빠르게 처리할 수 있습니다.
Stream Prioritization은 하나의 커넥션 내에 있는 여러 스트림들 간의 우선순위를 지정하는 기술입니다. 이를 통해 먼저 처리되어야 할 요청 및 응답을 담당하는 스트림에 높은 우선순위를 매겨 지연 시간을 최소화하고 성능을 향상시킬 수 있습니다. 스트림의 우선순위는 1~256 사이의 숫자로 표현할 수 있습니다. 숫자가 낮을수록 우선순위가 높습니다.
또한 부모 스트림과 자식 스트림간의 종속 관계를 통해 같은 우선순위를 가졌음에도 먼저 처리되어야 할 스트림을 지정할 수 있습니다. 이는 HTML 파일을 처리하는 스트림을 부모로 두고 HTML에 필요한 CSS, JS 파일등을 처리하는 스트림을 자식으로 두어서 조금 더 빠르고 효율적으로 처리할 수 있습니다.
Priority 프레임을 이용해서 자식 스트림의 우선순위를 변경할 수도 있습니다. 이때 자식 스트림은 부모 스트림보다 높은 우선순위를 가질 수 없습니다. (Priority 프레임은 스트림의 우선순위를 업데이트하기 위한 프레임으로, 프레임 헤더와 우선순위 정보를 포함하며, 종속관계가 있는 스트림일 경우 부모 스트림의 ID 또한 포함됩니다.)
Header Data Comperssion
- HTTP/1.1에서 헤더는 아무런 압축 없이 그대로 전송되었으며, 연속적으로 요청되는 HTTP 메시지의 헤더 값이 중복되는 부분들이 많아 네트워크 트래픽을 낭비하는 현상이 발생되었습니다. HTTP/2.0 부터는 HPACK 압축 알고리즘을 사용하여 중복된 헤더를 제거하고, 헤더에 대한 값을 압축 인코딩하여 네트워크 트래픽을 줄였습니다.
- HPACK 압축 알고리즘은 정적, 동적 테이블과 Huffman 압축 인코딩을 사용하는데, HTTP/2.0을 지원하는 환경에서는 모두 동일한 정적 테이블을 가지며, 자주 사용되는 헤더에 대한 정보를 가지고 있습니다. 이를 기반으로 통신하는 과정에서 동적으로 추가되는 헤더에 대한 인덱스를 생성하고 공유하여 이후 해당 헤더를 사용할 때는 이름이 아닌 index만을 기재합니다.
그리고 헤더에 대한 데이터는 Huffam 압축 인코딩을 통해서 압축하여 송.수신합니다. 이렇게 정적, 동적 테이블과 Huffman 압축 인코딩을 통해서 중복된 헤더를 제거하고 데이터를 압축하여 네트워크 트래픽량을 줄일 수 있습니다.
'네트워크 > HTTP' 카테고리의 다른 글
HTTP 상태코드란? (0) | 2023.01.29 |
---|---|
HTTP API 설계 방법과 HTTP 메서드와 특징 (1) | 2023.01.29 |
HTTP 메시지 구조 (0) | 2023.01.29 |
HTTPS( HyperText Transfer Protocol over Secure )란? (0) | 2023.01.24 |
REST(REpresentational State Transfer)란? (0) | 2022.12.16 |