1.0 HTTP/2
HTTP/2는 데이터 표현이 HTTP/1.1와는 크게 달라졌다.
- 스트림(1.1의 파이프라인에 가까운 것)을 사용해 마이너리 데이터를 다중으로 송수신하는 구조로 변경했다.
- 스트림 내 우선 순위 설정과 서버 사이드에서 데이터 통신을 하는 서버 사이드 푸시를 구현했다.
- 헤더가 압축되게 되었다.
단 메서드, 헤더, 스테이터스 코드, 바디라는 HTTP가 제공하는 네 개의 기본 요소는 바뀌지 않는다. HTTP/2의 목적은 통신 고속화뿐이다. 물론 빠른 속도는 중요하지만, 지금까지 해당 책에서 설명한 내용은 현재도 변함이 없다.
HTTP/1.0 부터 HTTP/1.1에 걸쳐, TCP 소켓 수준에서 보면 아래와 같은 개선이 이루어졌다.
기능 | 효과 |
캐시(max-age) | 통신 자체를 취소 |
캐시(ETag, Date) | 변경이 없으면 바디 전송을 취소 |
Keep-Alive | 액세스마다 연결에 걸리는 시간(1.5TTL)을 줄임 |
압축 | 응답 바디 크기 절감 |
청크 | 응답 전송 시작을 빠르게 함 |
파이프라이닝 | 통신 다중화 |
한 번의 통신이 연결되려면 접속 확립 요청을 주고받으며 몇 번씩 패킷이 오가게 된다. 이때 데이터 크기를 통신 속도로 나눈 시간만큼 통신이 걸린다. 통신 대기가 있으면, 그만큼 통신이 완료되는 시간은 길어진다.
지금까지 이루어진 고속화는 통신의 모든 부분에서의 고속화 기여했다. HTTP/2에서는 그동안 손보지 못한 헤더부 압축이나 규격화됐지만 여전히 활용되지 않은 파이프라이닝을 대체하는 구현이 추가됐다.
HTTP/2은 이전까지와는 완전히 다른 프로토콜이므로, 반대로 하위 호환성 문제가 일어나기 어렵다. HTTP의 텍스트 프로토콜 내부에서의 버전 전환이 아니다. HTTP/1.1과는 전혀 다른 프로토콜로 취급되므로 파이프라이닝과 같은 문제가 생길 수 없다.
1.1 스트림을 이용한 통신 고속화
HTTP/2의 가장 큰 변화는 텍스트 기반 프로토콜에서 바이너리 기반 프로토콜로 변화했다는 점이다. 각 데이터는 프레임 단위로 송수신을 한다. HTTP/1.1까지는 하나의 요청이 TCP 소켓을 독접했기 때문에, 하나의 오리지널 서버에 대해 2~6개의 TCP 접속을 해서 병렬화했다.
HTTP/2에서는 하나의 TCP 접속 안에 스트림이라는 가상의 TCP 소켓을 만들어 통신한다. 스트림은 프레임에 따른 플래그로 간단히 만들고 닫을 수 있는 규칙으로 되어있고, 일반 TCP 소켓과 같은 핸드세이크는 필요 없다. 그래서 ID 값과 TCP 통신 용량이 허락하는 한, 손쉽게 몇 만번의 접속이라도 병렬화 할 수 있다. 각 접속 상태의 state machine이 정의되어 있으며, 비슷한 모델로 되어 있는 것을 알 수 있다.
TCP는 닫힌 상태로 LISTEN하다가, 클라이언트에서 접속 요청이 있으면 비로소 ESTABLISH(통신 가능) 상태가 된다. HTTP/2의 스트림은 처음부터 LISTEN과 거의 같은 IDLE 상태이고, 헤더를 받으면 즉시 통신 가능한 OPEN 상태가 된다. 즉 통신할 수 있을 때까지의 단계가 줄어든 것이다.
요소 | 크기 | 의미 |
Length | 24 | 페이로드 크기 |
Type | 8 | 프레임 종류 |
Flags | 8 | |
R | 1 | 예약 영역 |
Stream Identifier | 31 | 스트림 식별자. 같음 값이면 같은 스트림 |
Frame Payroad | Length로 지정한 길이 | 프레임의 실제 데이터 |
공통 헤더 목록 중 주목해야 할 것은 Stream Identifier이다. HTTP/2에서는 의사적인 소켓으로 스트림이 만들어진다. TCP 소켓상의 데이터를 봐도, 스트림이라는 실체가 있는게 아니다. 같은 Stream Identifier를 가진 일련의 프레임은 수신 시에 그룹화되며, '같은 스트림에서 나온 데이터'로 취급된다.
종류 | 데이터 | 선택적 데이터 | 설명 |
HEADERS | 헤더 | 의존하는 스트림과 우선도, 베타 플래그 | 압축된 헤더, 우선도는 최초 헤더만 사용 가능 |
DATA | 데이터 | ||
PRIORITY | 의존하는 스트림 우선도, 베타 플래그 | ||
RST_STREAM | 오류 코드 | 오류 정보를 반환하고, 스트림을 바로 종료 | |
SETTINGS | 식별자(16비트), 설정값(32비트) |
||
PUSH_PROMISE | 스트림 ID | 요청 헤더 필드 | 서버 푸시 시작 예약 |
PING | 8바이트 데이터 | 응답 속도 측정용 프레임, PING을 받으면 ACK 플래그를 설정해 반환 | |
GOAWAY | 최종 스트림ID, 오류 코드 | 추가 디버그 정보 | 커넥션을 종료 |
WINDOW_UPDATE | 윈도우 크기 | 추가로 수신할 수 있는 데이터 크기 | |
CONTINUATION | HEADERS/PUSH_PROMISE에 이어지는 데이터 |
SETTINGS에서는 헤더 테이블 크기, 푸시 허가, 최대 병렬 스트림 수, 초기 윈도우 크기, 최대 프레임 크기, 최대 헤더 리스트 크기를 변경할 수 있다.
옵션은 공통 헤더의 Flags에 '이 데이터가 있어요'라고 지정한 비트가 켜지면 정해진 크기의 정보가 순서대로 프레임에 추가된다. 비트가 켜지지 않으면, 그 데이터는 프레임째 생략되므로 불필요한 데이터 때문에 데이터 길이가 늘어나는 것을 방지한다.
1.2 HTTP/2의 애플리케이션 계층
HTTP/1.0은 단순히 데이터를 운반하는 상자였다. 그 뒤로 Keep-Alive, 파이프라이닝 등 하위 계층의 통신 처리에 영향을 주는 기능도 포함하기 시작했지만, 이 책에서 몇 번씩이나 언급하고 있는 메서드와 경로, 헤더, 바디, 스테이터스 코드라는 네 가지 기본 요소가 존재한다는 것은 변함이 없다. 하지만 메서드와 경로, 스테이터스코드, 나머지 스테이터스 코드에 포함되어 있던 프로토콜 버전은 모두 유사 헤더 필드화되어 헤더 안에 들어간다. 상위 애플리케이션에서 본 기능성에는 변경이 없지만, 구현상으로는 헤더와 바디만 있다.
HTTP/1.1은 텍스트 프로토콜이었다. 헤더의 종단을 찾으려면 빈 줄을 찾을 때까지 1바이트씩 미리 읽어 발견할 필요가 있었다. 오류 처리도 있고 서버로서는 해석까지 포함해 순차적으로 처리할 수 밖에 없으므로 고급 병렬 처리는 어렵다. HTTP/2는 바이너리와되어, 처음에 프레임 크기가 들어간다. TCP 소켓 레이어에서는 테이블 프레임 단위로 쉽게 분리할 수 있으므로, 수신 측 TCP 소켓의 버퍼를 빠르게 비울 수 있고, 통신 상대에게 다음 데이터를 고속으로 요청할 수 있다.
바디는 Content-Length로 크기가 유일하게 정해지는 경우가 많고 청크 형식의 경우도 크기가 적혀 있기 때문에, 하나의 요청에 대한 로드 처리 비용은 다르지 않다. 그러나 요청이 여러 개일 때는 이야기가 다르다. HTTP/1.1에서는 청크 형식이라고해도 하나의 요청 중에 다른 요청을 처리할 수 없었다. 여섯 개의 TCP 세션으로 처리하더라도 무거운 응답이 여섯 개 있으면, 다른 통신을 전혀 할 수 없다. HTTP/2에서는 청크가 프레임으로 분할되어 있고 프레임끼리는 독립적이므로, 중간에 다른 프레임이 끼어들어도 문제가 없다.
1.3 플로 컨트롤
HTTP/2는 인터넷 4계층 모델 중 애플리케이션 층에 해당되지만, Transport 계층에 가까운 것을 내부에 가지고 있는 게 특징이다. 플로 컨트롤로 TCP 소켓과 거의 같은 기능을 구현했다. 물론 TCP가 패킷 순서 제어와 재전송 처리를 해주므로 구현은 단순하다. TCP 소켓과 HTTP/2 스트림의 관계는 OS 스레드와 그린 스레드와 비슷하다고 할 수 있다.
플로 컨트롤은 스트림을 효율적으로 흐르게 하려고 이용되는 통신량 제어 처리이다. 통신 속도가 지나치게 차이 나는 기기의 조합으로 통신할 때 빠른 쪽이 느린 쪽에 대량으로 패킷을 보내버려 처리할 수 없게되는 사태를 방지하는게 목적이다. 이를 실현하고자 구체적으로는 통신하는 곳의 윈도우 크기 관리를 사용한다. 윈도우 크기는 받아들일 수 있는 빈 버퍼 크기이다. 기본 초기 윈도우 크기는 64킬로바이트이다. 손신하는 쪽은 상대방의 최대 버퍼 크기만큼 데이터를 보낸다. 수신하는 쪽에서 전송된 패킷을 처리하고 버퍼에 여유가 생기면, WINDOW_UPDATE 프레임을 이용해 새로 생긴 여유 버퍼 크기를 송신하는 쪽에 반환한다. 소신하는 쪽에서 이 통지를 받으면, 새로 생긴 여유 버퍼를 채울 만큼 이어지는 데이터를 보낸다.
SETTINGS 프레임을 사용하면, 초기 윈도우 크기, 최대 병렬 스트림 수, 최대 프레임 크기, 최대 헤더 리스트 크기와 같은 속도에 관련된 매개변수를 조정할 수 있다.
1.4 서버 푸시
시맨틱스를 보면 HTTP/1.1과 HTTP/2는 거의 같지만, 서버 푸시로 불리는 기능만은 다르다. 서버 푸시를 이용해 우선순위가 높은 콘텐츠를 클라이언트가 요구하기 전에 전송할 수 있게 됐다. 단, 웹소켓처럼 양방향 통신을 실현하는 기술과 달리, 어디까지나 CSS와 자바스크립트, 이미지 등 웹페이즈를 구성하는 파일을 다운로드 하는 용도로 이용된다. 푸시된 콘텐츠는 사전에 캐시에 들어간다. 콘텐츠가 캐시에 들어간 뒤에 클라이언트가 그 파일을 요청하면, 곧바로 다운로드할 수 있는 것처럼 보인다. 채팅이나 구글 스프레드시트로 공동 편집 작업을 할 때 다른 사람이 입력한 주석을 서버가 배포하는 용도로는 사용할 수 없다.
1.5 HPACK을 이용한 헤더 압축
헤더는 HPACK이라는 방식으로 압축된다. HTTP 헤더는 정해진 이름이나 결과가 자주 출현하기때문에, 이를 외부 사전에 넣어두면 압축 후 크기가 작아진다. HTTP/2에서는 정적 테이블이라는 이름으로 사전에 빈번하게 출현하는 헤더 이름과 헤더 값을 테이블로 가지고 있다.
추가로 같은 커넥션에서 등장한 HTTP 헤더는 인덱스화되어 동작 테이블에 저장된다. 다시 등장할 때는 인덱스 값만으로 표헌할 수 있으므로 작은 크기로 송수신할 수 있다.
1.6 SPDY와 QUIC
HTTP/2의 역사를 말할 때 빠뜨릴 수 없는 것이 SPDY(스피디)이다. SPDY는 구글이 개발한 HTTP 대체 프로토콜로, 거의 그대로 HTTP/2가 되었다.
SPDY는 구글의 서비스 내부에서 사용됐고, 크롬과 파이어폭스, 인터넷 익스플로러, 사파리에도 구현됐습니다. 구글의 강점은 대규모 트래픽이 일어나는 웹 서비스와 점유율이 높은 브라우저를 모두 가지고 있는 점이다. 자사 서비스와 자사 브라우저 간에서만 활성화되는 프로토콜이라도 대규모 검증 실험을 할 수 있다. SPDY는 2010년 크롬에서 구현된 뒤에도 업그레이드를 거듭하며 2014년에 HTTP/2에 바톤을 넘기고 그 역할을 마쳤다. 처음으로 SPDY가 구현된 크롬도 2016년 5월에 출시된 크롬51에서 SPDY를 무효화하고 HTTP/2로 단일화했다.
구글이 SPDY를 개발했던 이유는 그동안 HTTP가 개선해온 전송 속도를 한층 더 향상시키기 위해서이다. 웹사이트 구성에 따라 효과가 크게 달리지기에 도입 효과는 30%에서 세 배 이상까지 여러 수치를 들 수 있지만 병렬 접속으로 블로킹이 줄어들어 작은 파일을 많이 전송할수록 빨라진다.
'웹사이트의 자바스크립트, CSS, 이미지 등은 가급적 적은 파일 수로 정리하는 고속화 방식'이 HTTP/1.1 시대에 빠른 웹사이트를 만드는 일반적인 기술이었다. SPDY와 HTTP/2 이 후, 파일 정리의 효과는 작아진다. 압축이 되긴 하지만, 헤더의 크기는 0이 아니고, 파일 크기가 작아질수록 패킷의 틈새가 낭비되는 경우도 증가한다. 또한 결합하는 편이 여전히 통신량은 줄어들지만, 자잘한 편이 변경이 있을 때 캐시가 살아남을 확률이 높아진다.
SPDY는 어디까지나 HTTP와 같은 층인 TCP 소켓상에 구현됐지만, 구글은 한 층 더 빠르게 하려고 UDP 소켓상에 QUIC(퀵)이라는 프로토콜을 준비했다. TCP는 접속 초기에 여러번 통신을 주고받을 필요가 있다. 에러를 정정하거나 순서를 정렬하기 위해 수신 통지를 반환해야 하는 등 고기능인 만큼 성능은 다소 떨어진다.
TCP와 쌍이 되는 경량 프로토콜이 UDP이다. TCP에서 재전송 처리, 혼잡 제어 등 고급 기능을 제거해서 처음 접속할 때의 니고시에이션을 가볍게 한 프로토콜이다. 패킷 유실시 재전송 처리, 통신 경로 혼잡 시 제어 등 QUIC은 많은 기능을 자체적으로 구현했다.
일반적인 HTTPS 통신에서는 TCP 핸드세이크를 실시한 후에 별도로 TLS 핸드세이크를 할 필요가 있어, 몇 번씩 왕복하며 패킷을 교환할 필요가 있었다. QUIC은 양쪽을 통합해, 더 적은 횟수의 통신으로 접속할 수 있다. 첫 접속에서도 1왕복 통신으로 니고시에이션하거나 재접속할 때는 니고시에이션 없이 0RTT로 재전송할 수 있는 구조로 되어 있다. 또 스마트폰이 3G/4G 회선에서 와이파이 연결로 전환했을 때의 재접속도 원활하게 됐다.
QUIC이 뛰어난 점은 재접속과 첫 통신 비용뿐만이 아니다. 이외의 TCP와 다름없는 듯하지만, HTTP/2와 협조해 동작함으로써 두 계 층에서 중복됐던 테스크를 단순화했다. 예를 들어, 거의 같은 기능이 TCP와 HTTP/2 양쪽에 있던 flow control이 일원화 됐다. 패킷 순서 정렬화에서는 애플리케이션 층을 모르는 TCP는 모든 패킷을 우직하게 정렬하지만, HTTP/2에서는 스트림 단위로 필요한 만큼 정렬한다.
2.0 Fetch API
Fetch API는 XMLHttpRequest와 마찬가지로 서버 액세스를 하는 함수이다. 자바스크립트에서 이요되며, 아래와 같은 특징이 있다.
- XMLHttpRequest보다 오리진 서버 밖으로의 액세스 등 CORS 제어가 쉬워진다.
- 자바스크립트의 모던한 비동기 처리 작성 기법인 프로미스를 따른다.
- 캐시를 제어할 수 있다.
- 리디렉트를 제어할 수 있다.
- 리퍼러 정책을 설정할 수 있다.
- Service Worker 내에서 이용할 수 있다.
Fetch API는 저수준 API로 소개되기도 하고 W3C사양에도 저수준이라고 적혀있다. 이 뜻은 캐시 등의 제어가 가능하다는 뜻이다.
2.1 Fetch API의 기본
아래는 Fetch API를 사용하는 예이다.
fetch("news.json", {
method: 'GET',
mode: 'cors',
credentials: 'include',
cache: 'default',
headers: {
'Content-Type': 'application/json'
}
}).then((response) => {
return response.json();
}).then((json) => {
console.log(json);
})
기본 요소는 아래 네가지이다.
- XMLHttpRequest처럼 오브젝트를 만드는게 아니라 fetch() 함수를 호출한다.
- fetch() 함수의 두 번째 인수는 옵션 오브젝트 (생략 가능)
- .then() 함수에 서버 응답이 돌아온 후에 호출되는 콜백을 넘겨준다.(프로미스)
- .then() 에 넘겨주는 콜백이 다시 시간이 걸리는 처리를 하고, 그 처리가 프로미스를 반환할 때는 .then()을 연결한다.
Fetch API의 첫 번째, .then() 절은 응답의 헤더 부근까지 읽기를 마친 시점에서 호출된다. Fetch API는 다양한 자료형을 지원해서, 첫 번째 .then() 절 안에서 바디를 어떤 형식으로 가져올지 메서드 호출로 결정한다. 그 뒤 처리는 서버에서의 읽기를 동반하므로, 다시 .then() 절을 호출해 다운로드가 끝나기를 기다린다. 해당 예제에서는 JSON 형식으로 읽어들인다.
아래는 Fetch API로 이용할 수 있는 메서드이다.
대응 | 메서드 목록 |
CORS 안전 | GET, HEAD, POST |
금지 메서드 | CONNECT, TRACE, TRACK |
GET, HEAD, POST의 세 가지는 CORS 안전으로서 특별하게 취급된다.
아래는 Fetch API CORS 모드이다.
설정값 | Fetch 기본 | XHR 기본 | 설명 |
cors | O | 다른 오리진 서버로의 액세스를 허용한다. | |
same-origin | 다른 오리진 서버로의 액세스를 오류로 한다. | ||
no-cors | O | CORS 접속은 무시되고 빈 응답이 돌아온다. |
쿠키 제한은 credentials에 설정한다. 설정할 수 있는 값은 아래와 같다. XMLHttpRequest의 경우는 withCredential 프로퍼티에 true를 설정하면 include를 설정한 것과 마찬가지가 된다.
아래는 Fetch API Credentials이다.
설정값 | Fetch 기본 | XHR 기본 | 설명 |
omit | O | 쿠키를 보내지 않는다. | |
same-origin | O | 출처가 같은 경우에만 쿠키를 보낸다. | |
include | 쿠키를 보낸다. |
앱은 속도와 보안 향상이라는 두 방향으로 발전했는데, Fetch API는 후자에 해당한다. 기본으로 더 엄격한 설정이 선택되어 있고, 필요에 따라 명시적으로 해제하는 설계로 되어 있다.
2.2 Fetch API만 할 수 있는 것
캐시 제어
Fetch API의 특징으로서 자주 소개되는 것이 캐시이다. 캐시는 세밀하게 제어할 수 있다. no-store와 reload, no-cache를 사용하면, 캐시 상태와 상관 없이 강제로 요청이 발생한다. 이 가운데 no-cache의 경우는 캐시에 대한 정보를 보내므로, 바디가 송신되지 않은 채 304 Not Modified를 수신할 가능성이 있다.
반대로 캐시를 적극적으로 하는 것이 force-cahce와 only-if-cached이다. Max-Age 헤더로 지정한 기한이 다 되어도 적극적으로 캐시를 사용한다. 후자는 캐시가 없으면 오류가 발생하고 외부로의 요청은 일어나지 않는다.
아래는 Fetch API 캐시 제어이다.
설정값 | 기본 | 설명 |
Default | O | 표준적인 브라우저 동작을 따른다. |
no-store | 캐시가 없는 것으로 해서 요청한다. 결과도 캐시하지 않는다. | |
Reload | 브라우저 새로고침과 같이 캐시가 없는 것으로 해서 요청한다. ETag 등은 보내지 않는다. 캐시 가능하면 결과를 캐시한다. | |
no-cache | 기한 내의 캐시가 있어도 HTTP 요청을 보낸다. 로컬 캐시의 ETag 등도 보내고, 서버가 304를 반환하면 캐시한 콘텐츠를 사용한다. | |
force-cache | 기한이 지난 캐시라도 있으면 이용한다. 없으면 HTTP 요청을 보낸다. | |
only-if-cached | 기한이 지난 캐시라도 있으면 사용한다. 없으면 오류가 발생한다. |
리디렉트 제어
아래는 Fetch API 리디렉트 제어이다.
설정값 | 기본 | 설명 |
follow | O | 리디렉트를 따라간다. (최대 20 리디렉트까지) |
manual | 리디렉트를 따라가지 않고 리디렉트가 있다는 것만 전달한다. | |
error | 네트워크 오류로 한다. |
manual 지정 시 리디렉트가 있으면, 응답 자체가 아니라 응답을 감싸 필터링된 결과를 응답으로서 반환한다. 이 응답은 type 속성에 opaqueredirect라는 문자열이 들어있는 이외의 정보는 필터링되어 아무것도 얻을 수 없다. 리디렉트 도중에는 보안상 누설돼선 곤란한 URL이나 헤더가 포함되기 때문이다. 바디는 null로 상태도 0이고 헤더도 얻을 수 없으므로, 실질적으로 '리디렉트가 있었다'는 것밖에 모른다. error와 달리 오류가 되지 않는 정도의 의미이다.
Service Worker 대응
Fetch API로는 할 수 있고 XMLHttpRequest로는 할 수 없는 잡다하게 있지만, 가장 큰 일은 Service Worker 대응일 것이다. 현재 Service Worker 내에서 외부 서비스로 접속할 때는 Fetch API만 사용할 수 있는 사양으로 되어 있다.
웹이 애플리케이션의 기능성을 지닐 수 있게 하려는 노력이 구글을 중심으로 이루어졌다. 그런 노력의 하나로서 애플리케이션의 생애주기와 통신 내용을 제어할 수 있게 하는 Service Worker가 개발됐다. Service Worker는 웹 서비스의 프론트엔드 자바스크립트와 서버 사이에서 동작하는 중간 레이어이다.
3.0 server-sent events
server-sent events는 HTML5의 기능 중 하나다. 기술적으로는 HTTP/1.1의 청크 형식을 이용한 통신 기능을 바탕으로 한다. 청크 형식은 거대한 파일 콘텐츠를 작게 나누어 전송하기 위한 통신 방식이었다. 청크 형식의 '조금씩 전송'한다는 특징을 응용해, 서버에서 임의의 시점에 클라이언트에 이벤트를 통지할 수 있는 기능을 실현했다.
HTTP는 기본적으로 클라이언트에서 요청을 서버로 보내고, 서버가 요청에 대해 응답하는 클라이언트/서버 모델이다. 통신의 시작은 클라이언트가 결정하고 , 클라이언트가 1회 요청하면 1회 응답이 발생하는 것이 기본 구성이다. 서버에서 정보를 돌려보내는 방법으로는 4장에서 소개한 코멧이 있다. 클라이언트에서 정기적으로 요청을 보내 서버 이벤트를 검출하거나 요청을 받은 상태에서 응답을 보류하는 방법이 자주 사용됐다.
server-sent events는 코멧의 롱 폴링과 청크 응답을 조합해, 한 번의 요청에 대해 서버에서 여러 이벤트 전송을 제공한다. 검증된 청크 방식을 사용하므로, 프록시 지원도 포함해 하위 호환성에 문제는 없다.
server-sent events는 청크 방식을 사용하지만, HTTP 위에 별도의 텍스트 프로토콜을 실었다. 이는 이벤트 스트림으로 불리며, MIME 타입은 text/event-stream이다.
아래는 text/event-stream 예제이다.
id: 10
event: ping
data: {"time": 2016-12-26T15:52:01+0000}
id: 11
data: Message from PySpa
data: #eng channel
텍스트의 문자 인코딩은 UTF-8이다. 데이터는 태그 뒤에 기술한다. 아래와 같이 네 종류의 태그가 있고, 빈 줄로 데이터를 구분한다.
태그 종류 | 설명 |
id | 이벤트를 식별하는 ID. 재전송 처리에서 이용된다. |
event | 이벤트 이용을 설정한다. |
data | 이벤트와 함께 보낼 데이터 |
retry | 재접속 대기 시간 파라미터 (밀리 초) |
자바스크립트 쪽에서는 server-sent events에 EventSource 클래스를 써서 액세스한다. 이 클래스는 이벤트 스트림을 해석하거나 접속이 끊겼을 때 재접속한다.
4.0 웹소켓
웹소켓은 서버/클라이언트 사이에 오버헤드가 적은 양방향 통신을 실현한다. 통신이 확립되면 서버/클라이언트 사이에서 일대일 통신을 수행한다. 프레임 단위로 송수신하지만, 상대방이 정해져 있으므로 전송할 곳에 관한 정보는 갖지 않는다. HTTP의 기본 요소 중에서 바디만 보내는 것과 같다. 프레임은 데이터 크기 등을 가질 뿐 오버헤드도 2바이트에서 14바이트밖에 안된다. 통신이 시작되면 양쪽에서 자유롭게 데이터를 주고받을 수 있다.
4.1 웹소켓은 스테이트풀
웹소켓이 HTTP 기반 프로토콜과 다른 점은 '스테이트풀 통신'이라는 점이다. HTTP는 속도를 위해 Keep-Alive 등의 복잡한 메커니즘도 갖추게 됐지만, 기본적으로 요청 단위로 접속이 끊어져도 시맨틱스 측면에서는 문제가 없다. 로드 밸런서(ELB)를 이용해 서버 여러 대에 분산해두고, 요청할 때마다 다른 서버가 응답해도 된다. Server-Sent Events도 전송한 ID를 일원적으로 관리해서 보증할 수 있다면, 요청을 다루는 서버가 바뀌더라도 이상 없도록 설계되어 있다.
웹소켓을 사용할 때 서버는 메모리에 데이터를 가진 상태로 통신하는 케이스가 많을 것이다. 문자 채팅과 같은 유스케이스에선 단일 서버에 모든 브라우저가 접속할 뿐만 아니라, 멤캐시나 레디스를 중계해서 부하를 분산시킬 수도 있다. 하지만 여러 사람이 실시간으로 동시에 플레이하는 게임처럼 부하 분산에 따른 지연을 허용할 수 없는 경우도 있다. 예를들어, 채팅이면 '대화방' 단위로 커넥션을 온 메모리로 관리한다. 이런 경우 일단 접속이 끊어졌을 때 재접속은 이전과 같은 서버로 연결할 필요가 있어, 단순한 HTTP 기반 로드 밸런서를 사용할 수 없다. 웹 소켓 운용 시에 클라이언트에서 서버를 지정해 재접속할 수 있도록 로드 밸런서를 사용하지 않은 채 운용하는 사례도 볼 수 있다. 그렇지 않으면 TCP 수준의 로드 밸런서를 구사하는 웹소켓 대응 로드 밸런서를 이용할 필요가 있다.
5.0 WebRTC
WebRTC는 이제까지 소개한 프로토콜과는 아주 다르다. 지금까지는 브라우저와 서버의 통신에 이용되는 프로토콜이었다면, WebRTC는 브라우저끼리의 P2P 통신에도 사용된다. RTC는 Realtime Communication의 줄임말로 화상 전화 등 실시간 커뮤니케이션을 구현하는 기반으로서 설계됐다.
통신 기반에 사용하는 transport 계층은 재전송 처리를 해주는 TCP가 아니라 오류 처리나 재전송 처리를 하지 않는 UDP를 메인으로 사용한다. P2P 통신이므로 상대 브라우저를 찾는 시그널링, 라우터 내부의 사설 주소만 있는 컴퓨터에서 동작하는 브라우저를 위해 NAT을 넘어 사설 주소만으로 통신할 수 있는 기술도 사용된다.
WebRTC 프로토콜의 자세한 설명은 "High Performance Browser Networking"을 참조하자.
6.0 HTTP 웹 푸시
HTTP 웹 푸시는 웹사이트에 스마트폰 애플리케이션과 같은 알림 기능을 제공하는 구조이다. 브라우저가 없는데 통신할 수 있는 것은 Service Worker에 있다. Service Worker 프론트엔드 브라우저의 HTML 랜더링 기능으로 웹 서버와 프론트엔드 중간에 있는 프록시 서버와 같은 존재다. addEventListener() 이벤트 핸들러를 등록하여 필요할 때만 실행된다.
Reference
- 리얼월드 HTTP
- https://web.dev/static/articles/performance-http2/image/http2-streams-messages-e837d74e21e27.svg
- https://docs.google.com/presentation/d/1r7QXGYOLCh4fcUq0jDdDwKJWNqWK1o4xMtYpKZCJYjM/preview?slide=id.p19
- https://blog.cloudflare.com/ko-kr/technical-breakdown-http2-rapid-reset-ddos-attack-ko-kr
- https://chat.openai.com/
'개발 서적 > 리얼월드 HTTP' 카테고리의 다른 글
Go 언어를 이용한 HTTP/2, HTML5 프로토콜 구현 (0) | 2024.04.14 |
---|---|
HTTP/2의 시맨틱스: 새로운 활용 사례 (0) | 2024.04.09 |
Go 언어를 이용한 HTTP/1.1 클라이언트 구현 (0) | 2024.03.08 |
HTTP/1.1의 시맨틱스: 확장되는 HTTP의 용도 (0) | 2024.03.03 |
HTTP/1.1 고속화와 안정성을 추구한 확장 (0) | 2024.02.14 |