HTTP/2를 통한 단방향 데이터 흐름을 위해 WebSocket 대신 SSE 사용
게시 됨: 2022-03-10웹 애플리케이션을 구축할 때 어떤 종류의 전달 메커니즘을 사용할 것인지 고려해야 합니다. 실시간 데이터로 작동하는 플랫폼 간 응용 프로그램이 있다고 가정해 보겠습니다. 실시간으로 주식을 사고 팔 수 있는 기능을 제공하는 주식 시장 응용 프로그램입니다. 이 응용 프로그램은 다른 사용자에게 다른 가치를 제공하는 위젯으로 구성됩니다.
서버에서 클라이언트로 데이터를 전달할 때 클라이언트 풀 또는 서버 푸시 의 두 가지 일반적인 접근 방식으로 제한됩니다. 모든 웹 애플리케이션의 간단한 예로서 클라이언트는 웹 브라우저입니다. 브라우저의 웹사이트가 서버에 데이터를 요청할 때 이를 클라이언트 풀( client pull )이라고 합니다. 반대로 서버가 사전에 웹사이트에 업데이트를 푸시하는 경우 이를 서버 푸시 라고 합니다.
요즘에는 이를 구현하는 몇 가지 방법이 있습니다.
- 긴/짧은 폴링(클라이언트 풀)
- WebSocket(서버 푸시)
- 서버에서 보낸 이벤트(서버 푸시).
비즈니스 사례에 대한 요구 사항을 설정한 후 세 가지 대안에 대해 자세히 살펴보겠습니다.
비즈니스 사례
주식 시장 애플리케이션을 위한 새로운 위젯을 신속하게 제공하고 전체 플랫폼을 재배포하지 않고 플러그 앤 플레이할 수 있으려면 이러한 위젯이 독립적이고 자체 데이터 I/O를 관리해야 합니다. 위젯은 어떤 식으로든 서로 연결되어 있지 않습니다. 이상적인 경우에는 그들 모두가 일부 API 엔드포인트를 구독하고 이 엔드포인트에서 데이터를 가져오기 시작할 것입니다. 새로운 기능의 시장 출시 시간 단축 외에도 이 접근 방식은 콘텐츠를 타사 웹사이트로 내보낼 수 있는 기능을 제공하는 반면 위젯은 필요한 모든 것을 자체적으로 가져옵니다.
여기서 주요 함정은 연결 수가 우리가 가진 위젯 수에 따라 선형적으로 증가하고 한 번에 처리되는 HTTP 요청 수에 대한 브라우저의 제한에 도달한다는 것입니다.
위젯이 수신할 데이터는 주로 숫자와 숫자 업데이트로 구성됩니다. 초기 응답에는 일부 시장 가치가 있는 10개의 주식이 포함됩니다. 여기에는 주식 추가/제거 업데이트 및 현재 표시된 주식의 시장 가치 업데이트가 포함됩니다. 모든 업데이트에 대해 가능한 한 빨리 소량의 JSON 문자열을 전송합니다.
HTTP/2는 동일한 도메인에서 오는 요청의 다중화를 제공합니다. 즉, 여러 응답에 대해 하나의 연결만 얻을 수 있습니다. 이것은 우리의 문제를 해결할 수 있을 것 같습니다. 우리는 데이터를 얻고 우리가 얻을 수 있는 것을 보기 위해 다양한 옵션을 탐색하는 것으로 시작합니다.
- 로드 밸런싱 및 프록시에 NGINX를 사용하여 동일한 도메인 뒤에 있는 모든 엔드포인트를 숨길 것입니다. 이렇게 하면 기본적으로 HTTP/2 멀티플렉싱을 사용할 수 있습니다.
- 우리는 모바일 장치의 네트워크와 배터리를 효율적으로 사용하고 싶습니다.
대안
긴 폴링

클라이언트 풀(Client pull)은 자동차 뒷좌석에 앉아 있는 성가신 아이가 끊임없이 "아직 거기에 있습니까?"라고 묻는 것과 같은 소프트웨어 구현입니다. 간단히 말해서 클라이언트는 서버에 데이터를 요청합니다. 서버에 데이터가 없고 응답을 보내기 전에 일정 시간 동안 기다립니다.
- 대기 중에 무언가가 팝업되면 서버는 이를 보내고 요청을 닫습니다.
- 보낼 것이 없고 최대 대기 시간에 도달하면 서버는 데이터가 없다는 응답을 보냅니다.
- 두 경우 모두 클라이언트는 데이터에 대한 다음 요청을 엽니다.
- 거품을 내고 헹구고 반복하십시오.
AJAX 호출은 동일한 도메인에 대한 요청이 기본적으로 다중화되어야 함을 의미하는 HTTP 프로토콜에서 작동합니다. 그러나 필요에 따라 작동하도록 하는 데 여러 문제가 발생했습니다. 위젯 접근 방식으로 식별한 몇 가지 함정:
헤더 오버헤드
모든 폴링 요청 및 응답은 완전한 HTTP 메시지이며 메시지 프레이밍에 전체 HTTP 헤더 세트를 포함합니다. 작은 빈도의 메시지가 있는 경우 헤더는 실제로 전송된 데이터의 더 큰 비율을 나타냅니다. 실제 유용한 페이로드는 전송된 총 바이트보다 훨씬 적습니다(예: 5KB 데이터에 대해 15KB 헤더).최대 지연 시간
서버가 응답한 후 클라이언트가 다음 요청을 보낼 때까지 더 이상 클라이언트에 데이터를 보낼 수 없습니다. 긴 폴링의 평균 대기 시간은 한 네트워크 전송에 가깝지만 최대 대기 시간은 응답, 요청, 응답의 세 가지 네트워크 전송 이상입니다. 그러나 패킷 손실 및 재전송으로 인해 TCP/IP 프로토콜의 최대 대기 시간은 네트워크 전송이 3회 이상입니다(HTTP 파이프라이닝으로 피할 수 있음). 직접 LAN 연결에서는 큰 문제가 아니지만 이동 중이거나 네트워크 셀을 전환하는 동안 문제가 됩니다. 이것은 SSE와 WebSocket에서 어느 정도 관찰되지만 그 효과는 폴링에서 가장 큽니다.연결 설정
많은 폴링 요청에 재사용 가능한 영구 HTTP 연결과 함께 사용하면 피할 수 있지만 연결을 유지하기 위해 짧은 시간 동안 폴링하는 모든 구성 요소의 시간을 적절하게 지정하는 것은 까다롭습니다. 결국 서버 응답에 따라 설문조사가 비동기화됩니다.성능 저하
부하가 걸리는 긴 폴링 클라이언트(또는 서버)는 메시지 대기 시간을 희생하면서 성능이 저하되는 자연스러운 경향이 있습니다. 이 경우 클라이언트에 푸시된 이벤트가 대기열에 추가됩니다. 이것은 실제로 구현에 따라 다릅니다. 우리의 경우 위젯에 추가/제거/업데이트 이벤트를 보낼 때 모든 데이터를 집계해야 합니다.시간 초과
긴 폴 요청은 서버가 클라이언트에 보낼 내용이 있을 때까지 보류 상태로 유지되어야 합니다. 이로 인해 프록시 서버가 너무 오랫동안 유휴 상태를 유지하면 연결이 닫힐 수 있습니다.다중화
이는 지속적인 HTTP/2 연결을 통해 응답이 동시에 발생하는 경우 발생할 수 있습니다. 폴링 응답이 실제로 동기화될 수 없기 때문에 이 작업을 수행하기가 까다로울 수 있습니다.
긴 폴링에서 경험할 수 있는 실제 문제에 대한 자세한 내용은 여기 에서 찾을 수 있습니다 .
웹소켓

서버 푸시 방법의 첫 번째 예로 WebSocket을 살펴보겠습니다.
MDN을 통해:
WebSockets는 사용자의 브라우저와 서버 간의 대화식 통신 세션을 열 수 있게 해주는 고급 기술입니다. 이 API를 사용하면 응답을 위해 서버를 폴링할 필요 없이 서버에 메시지를 보내고 이벤트 기반 응답을 받을 수 있습니다.
이것은 단일 TCP 연결을 통해 전이중 통신 채널을 제공하는 통신 프로토콜입니다.
HTTP와 WebSocket은 모두 OSI 모델의 응용 프로그램 계층에 위치하므로 계층 4의 TCP에 의존합니다.
- 애플리케이션
- 프레젠테이션
- 세션
- 수송
- 회로망
- 데이터 링크
- 물리적 인
RFC 6455는 WebSocket이 "HTTP 포트 80 및 443을 통해 작동하고 HTTP 프록시 및 중개자를 지원하도록 설계되었으므로" HTTP 프로토콜과 호환되도록 한다고 명시합니다. 호환성을 달성하기 위해 WebSocket 핸드셰이크는 HTTP 업그레이드 헤더를 사용하여 HTTP 프로토콜에서 WebSocket 프로토콜로 변경합니다.
Wikipedia의 WebSocket에 대해 알아야 할 모든 것을 설명하는 아주 좋은 기사도 있습니다. 나는 당신이 그것을 읽을 것을 권장합니다.
소켓이 실제로 우리를 위해 작동할 수 있다는 것을 확인한 후, 우리는 비즈니스 사례에서 소켓의 기능을 탐구하기 시작했고 벽을 연달아 성공했습니다.
프록시 서버 : 일반적으로 WebSocket 및 프록시에는 몇 가지 다른 문제가 있습니다.
- 첫 번째는 인터넷 서비스 제공자와 그들이 네트워크를 처리하는 방식과 관련이 있습니다. 반경 프록시가 포트를 차단하는 등의 문제.
- 두 번째 유형의 문제는 보안되지 않은 HTTP 트래픽 및 수명이 긴 연결을 처리하도록 프록시를 구성하는 방식과 관련이 있습니다(HTTPS를 사용하면 영향이 줄어듭니다).
- 세 번째 문제는 "WebSocket을 사용하면 HTTP 프록시가 아닌 TCP 프록시를 실행해야 합니다. TCP 프록시는 헤더를 삽입하거나 URL을 재작성하거나 전통적으로 HTTP 프록시가 처리하도록 했던 많은 역할을 수행할 수 없습니다.”
연결 수 : 6을 중심으로 하는 HTTP 요청에 대한 유명한 연결 제한은 WebSockets에 적용되지 않습니다. 50 소켓 = 50 연결. 50개의 소켓에 의한 10개의 브라우저 탭 = 500개의 연결 등. WebSocket은 데이터 전달을 위한 다른 프로토콜이므로 HTTP/2 연결을 통해 자동으로 다중화되지 않습니다(실제로 HTTP 위에서 전혀 실행되지 않음). 서버와 클라이언트 모두에서 사용자 정의 멀티플렉싱을 구현하는 것은 지정된 비즈니스 사례에서 소켓을 유용하게 만들기에는 너무 복잡합니다. 게다가, 이것은 구독하기 위해 클라이언트에서 일종의 API가 필요하고 우리가 없이는 배포할 수 없기 때문에 위젯을 플랫폼에 연결합니다.
로드 밸런싱(다중화 없음) : 모든 단일 사용자가
n
소켓을 열면 적절한 로드 밸런싱이 매우 복잡합니다. 서버에 과부하가 걸리고 소프트웨어 구현에 따라 새 인스턴스를 생성하고 이전 인스턴스를 종료해야 하는 경우 "재연결" 시 수행되는 작업은 시스템에 과부하를 일으킬 데이터에 대한 새로운 요청 및 새로 고침의 대규모 체인을 유발할 수 있습니다. . WebSocket은 서버와 클라이언트 모두에서 유지 관리해야 합니다. 현재 서버의 부하가 높은 경우 소켓 연결을 다른 서버로 이동할 수 없습니다. 닫았다가 다시 열어야 합니다.DoS : 이것은 일반적으로 WebSocket에 필요한 TCP 프록시가 처리할 수 없는 프런트 엔드 HTTP 프록시에 의해 처리됩니다. 소켓에 연결할 수 있으며 서버에 데이터가 넘쳐나기 시작합니다. WebSocket은 이러한 공격에 취약합니다.
바퀴의 재발 명: WebSocket을 사용하면 HTTP에서 스스로 처리해야 하는 많은 문제를 처리해야 합니다.
WebSocket의 실제 문제에 대한 자세한 내용은 여기에서 읽을 수 있습니다.
WebSocket의 좋은 사용 사례는 구현 문제보다 이점이 더 많은 채팅 및 멀티 플레이어 게임입니다. 이중 통신이 주요 이점이고 실제로 필요하지 않으므로 계속 진행해야 합니다.
영향
개발, 테스트 및 확장 측면에서 운영 오버헤드가 증가합니다. 폴링과 WebSocket을 모두 갖춘 소프트웨어와 IT 인프라입니다.
모바일 장치와 네트워크 모두에서 동일한 문제가 발생합니다. 이러한 장치의 하드웨어 설계는 안테나와 셀룰러 네트워크 연결을 활성 상태로 유지하여 개방형 연결을 유지합니다. 이로 인해 배터리 수명이 단축되고 열이 발생하며 경우에 따라 데이터에 대한 추가 요금이 부과됩니다.
하지만 모바일 장치에 여전히 문제가 있는 이유는 무엇입니까?
기본 모바일 장치가 인터넷에 연결하는 방법을 고려해 보겠습니다.
모바일 네트워크 작동 방식에 대한 간단한 설명: 일반적으로 모바일 장치에는 셀에서 데이터를 수신할 수 있는 저전력 안테나가 있습니다. 이렇게 하면 장치가 들어오는 호출에서 데이터를 수신하면 호출을 설정하기 위해 전이중 안테나를 부팅합니다. 전화를 걸거나 인터넷에 접속할 때마다 동일한 안테나가 사용됩니다(WiFi를 사용할 수 없는 경우). 전이중 안테나는 셀룰러 네트워크에 대한 연결을 설정하고 일부 인증을 수행해야 합니다. 연결이 설정되면 네트워크 요청을 수행하기 위해 장치와 셀 간에 약간의 통신이 이루어집니다. 인터넷 요청을 처리하는 모바일 서비스 공급자의 내부 프록시로 리디렉션됩니다. 그때부터 절차는 이미 알려져 있습니다. www.domainname.ext
가 실제로 있는 DNS를 요청하고 리소스에 대한 URI를 수신하고 결국에는 리디렉션됩니다.
이 프로세스는 상상할 수 있듯이 상당히 많은 배터리 전력을 소모합니다. 휴대폰 판매업체가 대기 시간을 며칠, 통화 시간을 단 몇 시간으로 주는 이유다.
WiFi가 없으면 WebSocket과 폴링 모두 거의 지속적으로 작동하는 전이중 안테나가 필요합니다. 따라서 데이터 소비가 증가하고 전력 소모가 증가하며 장치에 따라 발열도 증가합니다.
상황이 암울해 보일 때쯤이면 애플리케이션에 대한 비즈니스 요구 사항을 재고해야 할 것 같습니다. 우리가 뭔가를 놓치고 있습니까?
SSE

MDN을 통해:

“EventSource 인터페이스는 Server-Sent Events를 수신하는 데 사용됩니다. HTTP를 통해 서버에 연결하고 연결을 닫지 않고 텍스트/이벤트 스트림 형식으로 이벤트를 수신합니다."
폴링의 주요 차이점은 하나의 연결만 얻고 이를 통해 이벤트 스트림을 계속 진행한다는 것입니다. 긴 폴링은 모든 풀(pull)에 대해 새로운 연결을 생성합니다. 즉, 헤더 오버헤드 및 우리가 그곳에서 직면한 기타 문제를 포함합니다.
html5doctor.com을 통해:
서버 전송 이벤트는 서버에서 발생하고 브라우저에서 수신하는 실시간 이벤트입니다. 실시간으로 발생한다는 점에서 WebSocket과 유사하지만 서버와의 단방향 통신 방법에 가깝습니다.
이상해 보이지만 고려해보면 주요 데이터 흐름은 서버에서 클라이언트로, 훨씬 적은 경우에 클라이언트에서 서버로입니다.
데이터 전달의 주요 비즈니스 사례에 이것을 사용할 수 있을 것 같습니다. 프로토콜이 단방향이고 클라이언트가 이를 통해 서버에 메시지를 보낼 수 없기 때문에 새 요청을 보내 고객 구매를 해결할 수 있습니다. 이것은 결국 모바일 장치에서 부팅하기 위해 전이중 안테나의 시간 지연을 갖게 될 것입니다. 그러나 우리는 때때로 일어나는 일을 감당할 수 있습니다. 이 지연은 결국 밀리초 단위로 측정됩니다.
독특한 기능
- 연결 스트림은 서버에서 수신되며 읽기 전용입니다.
- 그들은 특별한 프로토콜이 아닌 지속적인 연결을 위해 일반 HTTP 요청을 사용합니다. 기본적으로 HTTP/2를 통한 멀티플렉싱
- 연결이 끊어지면 EventSource는 오류 이벤트를 발생시키고 자동으로 다시 연결을 시도합니다. 서버는 클라이언트가 다시 연결을 시도하기 전에 시간 초과를 제어할 수도 있습니다(자세한 내용은 나중에 설명).
- 클라이언트는 메시지와 함께 고유한 ID를 보낼 수 있습니다. 클라이언트가 연결이 끊긴 후 다시 연결을 시도하면 마지막으로 알려진 ID를 보냅니다. 그런 다음 서버는 클라이언트가
n
메시지를 놓친 것을 확인하고 재연결 시 누락된 메시지의 백로그를 보낼 수 있습니다.
샘플 클라이언트 구현
이러한 이벤트는 클릭 이벤트와 같이 브라우저에서 발생하는 일반 JavaScript 이벤트와 유사하지만 이벤트의 이름과 이벤트와 관련된 데이터를 제어할 수 있다는 점만 다릅니다.
클라이언트 측의 간단한 코드 미리보기를 살펴보겠습니다.
// subscribe for messages var source = new EventSource('URL'); // handle messages source.onmessage = function(event) { // Do something with the data: event.data; };
예제에서 볼 수 있는 것은 클라이언트 측이 상당히 단순하다는 것입니다. 소스에 연결하고 메시지 수신을 기다립니다.
서버가 HTTP를 통해 또는 전용 서버 푸시 프로토콜을 사용하여 웹 페이지에 데이터를 푸시할 수 있도록 사양은 클라이언트에 'EventSource' 인터페이스를 도입합니다. 이 API를 사용하는 것은 `EventSource` 객체를 생성하고 이벤트 리스너를 등록하는 것으로 구성됩니다.
WebSocket에 대한 클라이언트 구현은 이와 매우 유사합니다. 소켓의 복잡성은 IT 인프라 및 서버 구현에 있습니다.
이벤트 소스
각 EventSource
개체에는 다음 멤버가 있습니다.
- URL: 구성 중에 설정됩니다.
- 요청: 처음에는 null입니다.
- 재연결 시간: ms 단위 값(사용자 에이전트 정의 값).
- 마지막 이벤트 ID: 처음에는 빈 문자열입니다.
- 준비 상태: 연결 상태입니다.
- 연결 중 (0)
- 오픈 (1)
- 휴무 (2)
URL을 제외하고 모두 비공개로 처리되어 외부에서 액세스할 수 없습니다.
내장 이벤트:
- 열려있는
- 메세지
- 오류
연결 끊김 처리
연결이 끊어지면 브라우저에서 자동으로 다시 연결합니다. 서버는 연결을 영구적으로 재시도하거나 종료하기 위해 시간 초과를 보낼 수 있습니다. 이러한 경우 브라우저는 시간 초과 후 다시 연결을 시도하거나 연결이 종료 메시지를 받은 경우 전혀 시도하지 않는 것에 따릅니다. 상당히 간단해 보이지만 실제로는 그렇습니다.
샘플 서버 구현
클라이언트가 그렇게 단순하다면 서버 구현이 복잡할까요?
SSE용 서버 핸들러는 다음과 같습니다.
function handler(response) { // setup headers for the response in order to get the persistent HTTP connection response.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }); // compose the message response.write('id: UniqueID\n'); response.write("data: " + data + '\n\n'); // whenever you send two new line characters the message is sent automatically }
응답을 처리할 함수를 정의합니다.
- 헤더 설정
- 메시지 작성
- 보내다
send()
또는 push()
메서드 호출이 표시되지 않습니다. 이는 표준이 다음 예와 같이 두 개의 \n\n
문자를 수신하는 즉시 메시지가 전송되도록 정의하기 때문입니다. response.write("data: " + data + '\n\n');
. 이렇게 하면 메시지가 즉시 클라이언트에 푸시됩니다. data
는 이스케이프된 문자열이어야 하며 끝에 새 줄 문자가 없어야 합니다.
메시지 구성
앞서 언급했듯이 메시지에는 몇 가지 속성이 포함될 수 있습니다.
- ID
필드 값에 U+0000 NULL이 포함되어 있지 않으면 마지막 이벤트 ID 버퍼를 필드 값으로 설정합니다. 그렇지 않으면 필드를 무시하십시오. - 데이터
필드 값을 데이터 버퍼에 추가한 다음 단일 U+000A LINE FEED(LF) 문자를 데이터 버퍼에 추가합니다. - 이벤트
이벤트 유형 버퍼를 필드 값으로 설정합니다. 이것은event.type
이 사용자 정의 이벤트 이름을 가져오도록 합니다. - 다시 해 보다
필드 값이 ASCII 숫자로만 구성된 경우 필드 값을 10진수의 정수로 해석하고 이벤트 스트림의 재연결 시간을 해당 정수로 설정하십시오. 그렇지 않으면 필드를 무시하십시오.
다른 것은 무시됩니다. 우리는 우리 자신의 분야를 소개할 수 없습니다.
event
가 추가된 예:
response.write('id: UniqueID\n'); response.write('event: add\n'); response.write('retry: 10000\n'); response.write("data: " + data + '\n\n');
그런 다음 클라이언트에서 이것은 다음과 같이 addEventListener
로 처리됩니다.
source.addEventListener("add", function(event) { // do stuff with data event.data; });
다른 ID를 제공하는 한 새 줄로 구분하여 여러 메시지를 보낼 수 있습니다.
... id: 54 event: add data: "[{SOME JSON DATA}]" id: 55 event: remove data: JSON.stringify(some_data) id: 56 event: remove data: { data: "msg" : "JSON data"\n data: "field": "value"\n data: "field2": "value2"\n data: }\n\n ...
이는 데이터로 할 수 있는 작업을 크게 단순화합니다.
특정 서버 요구 사항
백엔드에 대한 POC 중에 SSE를 제대로 구현하기 위해 해결해야 할 몇 가지 세부 사항이 있음을 확인했습니다. NodeJS, Kestrel 또는 Twisted와 같은 이벤트 루프 기반 서버를 사용하는 최상의 시나리오입니다. 스레드 기반 솔루션을 사용하면 연결당 스레드 수 → 1000개 연결 = 1000개 스레드가 있다는 아이디어입니다. 이벤트 루프 솔루션을 사용하면 1000개의 연결에 대해 하나의 스레드를 갖게 됩니다.
- HTTP 요청이 이벤트 스트림 MIME 유형을 수락할 수 있다고 말하는 경우에만 EventSource 요청을 수락할 수 있습니다.
- 새 이벤트를 내보내려면 연결된 모든 사용자의 목록을 유지 관리해야 합니다.
- 끊어진 연결을 수신 대기하고 연결된 사용자 목록에서 제거해야 합니다.
- 다시 연결하는 클라이언트가 누락된 메시지를 따라잡을 수 있도록 선택적으로 메시지 기록을 유지 관리해야 합니다.
예상대로 작동하며 처음에는 마술처럼 보입니다. 애플리케이션이 효율적인 방식으로 작동하는 데 필요한 모든 것을 얻을 수 있습니다. 사실이라고 하기에는 너무 좋아 보이는 모든 것과 마찬가지로 우리는 때때로 해결해야 할 몇 가지 문제에 직면합니다. 그러나 구현하거나 둘러보기가 복잡하지 않습니다.
레거시 프록시 서버는 경우에 따라 짧은 시간 초과 후에 HTTP 연결을 끊는 것으로 알려져 있습니다. 이러한 프록시 서버로부터 보호하기 위해 작성자는 약 15초마다 주석 줄(':' 문자로 시작하는 줄)을 포함할 수 있습니다.
이벤트 소스 연결을 서로 연관시키거나 이전에 제공된 특정 문서와 관련시키려는 작성자는 개별 클라이언트가 여러 IP 주소(여러 프록시 서버를 가짐으로 인해)를 가질 수 있고 개별 IP 주소가 여러 클라이언트(프록시 서버 공유로 인해). 문서가 제공될 때 고유 식별자를 포함하고 연결이 설정되면 해당 식별자를 URL의 일부로 전달하는 것이 좋습니다.
작성자는 또한 HTTP 청크가 이 프로토콜의 안정성에 예기치 않은 부정적인 영향을 미칠 수 있다는 점에 주의해야 합니다. 특히 청크가 타이밍 요구 사항을 인식하지 못하는 다른 계층에서 수행되는 경우에 그렇습니다. 이것이 문제인 경우 이벤트 스트림을 제공하기 위해 청크를 비활성화할 수 있습니다.
HTTP의 서버별 연결 제한을 지원하는 클라이언트는 각 페이지에 동일한 도메인에 대한 EventSource가 있는 경우 사이트에서 여러 페이지를 열 때 문제가 발생할 수 있습니다. 작성자는 연결당 고유한 도메인 이름을 사용하는 비교적 복잡한 메커니즘을 사용하거나, 사용자가 페이지별로 EventSource 기능을 활성화 또는 비활성화하도록 허용하거나, 공유 작업자를 사용하여 단일 EventSource 개체를 공유하여 이를 방지할 수 있습니다.
브라우저 지원 및 Polyfills: Edge는 이 구현보다 뒤쳐져 있지만, 이를 저장할 수 있는 polyfill을 사용할 수 있습니다. 그러나 SSE의 가장 중요한 경우는 IE/Edge가 실행 가능한 시장 점유율이 없는 모바일 장치에 대해 만들어집니다.
사용 가능한 폴리필 중 일부:
- 야플
- 암브텍
- 레미
비연결 푸시 및 기타 기능
특정 통신 사업자에 연결된 모바일 핸드셋의 브라우저와 같이 제어된 환경에서 실행되는 사용자 에이전트는 네트워크의 프록시에 대한 연결 관리를 오프로드할 수 있습니다. 이러한 상황에서 적합성을 위한 사용자 에이전트는 핸드셋 소프트웨어와 네트워크 프록시를 모두 포함하는 것으로 간주됩니다.
예를 들어, 모바일 장치의 브라우저는 연결을 설정한 후 지원 네트워크에 있음을 감지하고 네트워크의 프록시 서버가 연결 관리를 인계받을 것을 요청할 수 있습니다. 이러한 상황에 대한 타임라인은 다음과 같을 수 있습니다.
- 브라우저는 원격 HTTP 서버에 연결하고 EventSource 생성자에서 작성자가 지정한 리소스를 요청합니다.
- 서버는 가끔 메시지를 보냅니다.
- 두 메시지 사이에서 브라우저는 TCP 연결을 유지하는 것과 관련된 네트워크 활동을 제외하고는 유휴 상태임을 감지하고 절전 모드로 전환하여 전원을 절약하기로 결정합니다.
- 브라우저가 서버에서 연결을 끊습니다.
- 브라우저는 네트워크의 서비스에 연결하고 "푸시 프록시"인 서비스가 대신 연결을 유지하도록 요청합니다.
- "푸시 프록시" 서비스는 원격 HTTP 서버에 접속하고 작성자가 EventSource 생성자에서 지정한 리소스를 요청합니다(
Last-Event-ID
HTTP 헤더 등을 포함할 수 있음). - 브라우저는 모바일 장치가 절전 모드로 전환되도록 합니다.
- 서버는 다른 메시지를 보냅니다.
- "푸시 프록시" 서비스는 OMA 푸시와 같은 기술을 사용하여 모바일 장치에 이벤트를 전달합니다. 모바일 장치는 이벤트를 처리할 만큼만 깨우고 다시 절전 모드로 돌아갑니다.
이렇게 하면 총 데이터 사용량을 줄일 수 있으므로 상당한 전력 절감 효과를 얻을 수 있습니다.
사양에 정의된 대로 기존 API 및 텍스트/이벤트 스트림 와이어 형식을 구현하고 더 분산된 방식으로(위에서 설명한 대로), 다른 적용 가능한 사양에 의해 정의된 이벤트 프레이밍 형식이 지원될 수 있습니다.
요약
서버 및 클라이언트 구현을 포함하여 길고 철저한 POC 후에 SSE가 데이터 전달 문제에 대한 답인 것 같습니다. 여기에도 몇 가지 함정이 있지만 쉽게 고칠 수 있는 것으로 판명되었습니다.
이것이 우리의 프로덕션 설정이 최종적으로 보이는 방식입니다.

NGINX에서 다음을 얻습니다.
- 다른 위치에 있는 API 엔드포인트에 대한 프록시
- HTTP/2 및 연결을 위한 멀티플렉싱과 같은 모든 이점
- 로드 밸런싱;
- SSL.
이렇게 하면 모든 엔드포인트에서 별도로 수행하는 대신 데이터 전달 및 인증서를 한 곳에서 관리할 수 있습니다.
이 접근 방식을 통해 얻을 수 있는 주요 이점은 다음과 같습니다.
- 데이터 효율성;
- 더 간단한 구현;
- HTTP/2를 통해 자동으로 다중화됩니다.
- 클라이언트의 데이터에 대한 연결 수를 하나로 제한합니다.
- 프록시에 대한 연결을 오프로드하여 배터리를 절약하는 메커니즘을 제공합니다.
SSE는 빠른 업데이트를 제공하는 다른 방법에 대한 실행 가능한 대안이 아닙니다. 모바일 장치에 대한 최적화와 관련하여 자체 리그에 있는 것처럼 보입니다. 대안에 비해 구현에 오버헤드가 없습니다. 서버 측 구현 측면에서 폴링과 크게 다르지 않습니다. 클라이언트에서는 초기 구독이 필요하고 이벤트 핸들러를 할당해야 하기 때문에 폴링보다 훨씬 간단합니다. WebSocket이 관리되는 방식과 매우 유사합니다.
간단한 클라이언트-서버 구현을 원하면 코드 데모를 확인하십시오.
자원
- "양방향 HTTP에서 긴 폴링 및 스트리밍 사용에 대한 알려진 문제 및 모범 사례", IETF(PDF)
- W3C 권장 사항, W3C
- "WebSocket은 HTTP/2에서 살아남을 수 있을까요?", Allan Denis, InfoQ
- HTML5 Rocks의 Eric Bidelman, "서버에서 보낸 이벤트로 스트리밍 업데이트"
- "HTML5 SSE를 사용한 데이터 푸시 앱", Darren Cook, O'Reilly Media