🌎Http

HTTP 스트리밍 정리: SSE와 Chunked Transfer Encoding의 관계

limdaeil 2026. 1. 14. 16:21

🔖Contents

1. HTTP 스트리밍

HTTP 요청-응답 모델에서는 클라이언트가 요청을 보내면 서버가 전체 응답을 한 번에 전송하고 연결을 종료하는 경우가 일반적입니다.

HTTP 스트리밍은 서버가 연결을 유지한 채, 응답 본문을 시간에 따라 조금씩(점진적으로) 전송합니다.
서버에서 생성되는 데이터(이벤트, 로그, 진행률 등)를 즉시 클라이언트에 전달할 수 있어 실시간 업데이트에 유리합니다.

1.1 HTTP 스트리밍 작동 방식

1) 청크 전송 인코딩

HTTP 스트리밍을 구현하는 한 가지 방법은 "청크 전송 인코딩"을 사용하는 것입니다. 서버는 응답의 전체 크기를 미리 알 필요 없이 데이터를 청크 단위로 전송합니다. 각 청크는 준비되는 즉시 전송되므로 지연을 줄이고, 스트리밍 형태의 응답이 가능합니다.

RFC 9112

청크 전송 인코딩(Chunked Transfer Encoding)HTTP/1.1에서 도입된 전송 방식으로, 응답 본문의 전체 크기를 미리 알 수 없을 때 데이터를 여러 조각(청크)으로 나누어 스트리밍처럼 전송하는 방법입니다.

https://www.rfc-editor.org/rfc/rfc9112.html#name-chunked-transfer-coding

 

2) 서버 전송 이벤트(SSE)

또 다른 방식으로 서버 전송 이벤트(SSE)가 있습니다. SSE는 서버가 클라이언트로 이벤트를 스트리밍하기 위해 설계된 상위 수준의 이벤트 스트림 프로토콜입니다. 서버는 지속적인 연결을 유지하며, 구조화된 형식의 텍스트 이벤트를 클라이언트로 푸시합니다.

실시간 알림, 뉴스/로그 스트리밍, 서버가 클라이언트로 데이터를 지속적으로 푸시해야 하는 단방향 실시간 통신에서 널리 사용됩니다.

RFC 8895

Server-Sent Events (SSE)는 서버가 클라이언트(브라우저)로 실시간 이벤트를 지속적으로 푸시하는 단방향 통신 기술입니다.
HTTP 연결을 열어둔 채 서버에서 클라이언트로 데이터를 스트리밍할 수 있습니다.

https://www.rfc-editor.org/rfc/rfc8895.html

1.2 HTTP 스트리밍을 흉내낸 방식

폴링과 롱 폴링은 ‘그냥 HTTP’이며, HTTP를 실시간처럼 쓰는 기법일 뿐 스트리밍 기술은 아닙니다.

1) 폴링

HTTP 폴링 방식은 전통적인 AJAX 애플리케이션에서 사용되던 방식으로, 일정 주기마다 서버로 HTTP 요청을 보내 데이터를 가져옵니다. 새로운 데이터가 있든 없든 주기적으로 요청과 응답을 반복합니다.

2) 롱 폴링

롱 폴링은 폴링과 유사하지만, 서버가 요청을 받았을 때 바로 응답할 데이터가 없다면 일정 시간 응답을 지연시킵니다.
그리고 데이터가 생기는 순간 응답하거나, 타임아웃 시 빈 응답을 내려줄 수 있습니다. 클라이언트는 응답을 받는 즉시(보통) 다시 요청을 보내며, 결과적으로 “긴 요청을 유지했다가 이벤트가 발생하면 응답”하는 형태로 동작합니다.

 

또한 HTTP 연결 재사용(keep-alive)을 통해 매 요청마다 TCP 연결을 새로 맺는 오버헤드를 줄일 수 있습니다.
이는 폴링에도 적용되지만, 특히 요청 간격이 짧은 롱 폴링에서 더 효과적일 수 있습니다.

다만 롱 폴링 역시 요청을 반복해서 보내야 하므로 HTTP 헤더 오버헤드가 존재합니다. 업데이트가 매우 빈번한 상황에서는 “요청하자마자 응답 → 즉시 다음 요청”이 반복되어 폴링과 체감상 큰 차이가 없어질 수 있습니다.

1.3 SSE와 비교

특성 SSE (Server-Sent Events) WebSocket Polling (Short & Long)
통신 방향 서버 → 클라이언트 (단방향) 클라이언트 ↔ 서버 (양방향) 클라이언트 요청 → 서버 응답
프로토콜 HTTP WS (WebSocket 프로토콜)
HTTP 핸드셰이크 후 업그레이드
HTTP
연결 유지 하나의 HTTP 연결 지속
(text/event-stream)
전용 TCP 연결 (전이중 통신) 요청마다 연결 생성/해제
또는 일정 시간 유지
구현 복잡성 간단 (표준 HTTP) 복잡 (별도 라이브러리/서버 설정) 간단 (정기적 요청)
인프라 호환성 HTTP 인프라에 친화적 일부 인프라에서 제한적 HTTP 인프라에 친화적
자원 효율성 비교적 효율적
(단방향 지속 연결)
높은 자원 효율 (지속 연결) 낮음 (잦은 요청/응답)
자동 재연결 브라우저 내장 지원
(EventSource)
수동 구현 또는 외부 라이브러리 필요 해당 없음
데이터 형식 주로 텍스트 (UTF-8 인코딩) 텍스트 및 바이너리 모두 지원 텍스트 기반
주요 용도 실시간 뉴스 로그 스트리밍 주식 시세 등 채팅 게임 협업 편집 등 양방향 단순 주기적 갱신 트래픽 적은 환경

2. SSE 이해

  • 서버에서 클라이언트로 실시간 데이터를 단방향으로 지속적으로 보내는 기술
  • 연결이 이뤄지면 Client 의 별도 추가 요청 없어도 서버가 event를 전송(push) 한다.
  • HTTP 프로토콜을 사용한다.

SSE 의 사용 목적

  • 웹 브라우저에서 새로고침이나 조작 없이 실시간으로 데이터 업데이트, 알림 등을 구현할 때 사용

SSE의 장점

장점 항목 설명
단순한 구현 - WebSocket에 비해 서버 설정 및 코딩이 간단함
HTTP 기반 - 기존 HTTP 인프라(로드 밸런서, 프록시, 방화벽)와 호환성이 좋음
- 별도 포트/프로토콜 전환 부담이 적음
자원 효율성 - 단방향 통신에 최적화되어 비교적 자원 소모가 적음
자동 재연결 - 브라우저의 EventSource 객체에 자동 재연결 기능 내장
명확한 의미론 - 단방향 데이터 흐름이 직관적이라 애플리케이션 로직이 단순함

SSE의 단점

단점 항목 설명
단방향 통신 - 서버 → 클라이언트만 가능
- 클라이언트 → 서버는 별도 HTTP 요청 필요
텍스트 기반 - 기본적으로 텍스트 위주
- 바이너리 전송은 인코딩(Base64 등) 필요
HTTP/1.1 연결 제한 - 브라우저/호스트당 동시 연결 수 제한(보통 6개)이 존재할 수 있음

2.1 동작 방식

1)클라이언트 요청

  • 클라이언트가 SSE 연결 요청을 보냄
  • 요청을 보낼 때 HTTP 요청 헤더에는 Accept: text/event-stream 포함
  • 이는 곳 “이제부터 서버가 무슨 일이 생기면 계속 알려줘. 나는 듣기만 할게”라는 의미

2)서버 응답 및 연결 유지

  • 서버가 요청을 수락하고 응답함
  • 응답할 때는 Content-Typetext/event-stream로 설정
  • Connection 헤더를 포함하여 연결 유지
  • 하나의 TCP 연결을 유지하면서 데이터 전송 가능

3)실시간 메시지 전송

  • 서버에서 이벤트 발생 시 메시지를 클라이언트에 전송
  • 클라이언트는 수신한 데이터로 필요한 작업 수행 (예: 화면 업데이트)
  • 클라이언트 → 서버 방향의 통신은 별도 HTTP 요청 필요

4)자동 재연결

  • 연결이 끊기면 클라이언트가 자동으로 재연결 시도
  • EventSource 객체가 재접속 기능 내장
  • 메시지에 id가 있다면, 재접속 시 Last-Event-ID를 통해 이어받을 수 있음

5)연결 종료

  • 클라이언트 또는 서버가 명시적으로 연결을 종료할 수 있음
  • 불필요한 연결을 정리하여 자원 낭비를 방지

HTTP Connection 유지 방법

  • HTTP/1.0: Connection: keep-alive로 연결 유지
  • HTTP/1.1 이상: 기본적으로 keep-alive가 기본이므로 별도 설정이 필요 없는 경우가 많음

2.2 HTTP 헤더

 구분 설명
Request Header 헤더에 Accept: text/event-stream을 포함
Response Header 서버는 Content-Type: text/event-streamCache-Control: no-store를 포함하여 응답

Stream Event Format

항목 설명
data - data:로 시작하며 전송할 텍스트 데이터를 담음
id - id:로 시작하며 메시지에 고유 ID 부여
- 재접속 시 이 ID로 이어받기 가능
event - event:로 시작하며 이벤트 타입 명시
- 타입별 처리 가능
retry - retry:로 시작하며 재접속 시도 간격(ms) 지정
  • 주석: :로 시작하는 줄은 주석이며 클라이언트에서 무시됨
  • 메시지 구분: 각 메시지는 \n\n(두 번 줄바꿈)으로 구분됨
  • 인코딩: UTF-8

3. SSE와 청크 전송 인코딩 관계

Spring Boot(Spring MVC)에서 제공하는 SseEmitter로 SSE를 구현한다고 가정하겠습니다.
HTTP/1.1 환경에서 SSE는 내부적으로 청크 전송 인코딩(Chunked Transfer Encoding)을 통해 스트리밍 응답이 전달될 수 있고, HTTP/2에서는 DATA 프레임 기반으로 스트림이 전달됩니다. (HTTP/3에서의 SSE 동작도 확장 주제로 학습하면 좋습니다.)

1. 청크 전송 인코딩을 사용하는 이유

HTTP 응답 본문을 전송할 때는 보통 다음 중 하나를 선택합니다.

  1. Content-Length를 명시: 전체 크기를 미리 알리고, 그만큼 전송
  2. Transfer-Encoding: chunked: 전체 크기를 알 수 없으니, 데이터가 생길 때마다 조각(청크)으로 전송

SSE는 서버가 언제, 얼마나 많은 데이터를 보낼지 미리 알 수 없는 지속적인 스트림입니다. 따라서 응답 전체 길이를 나타내는 Content-Length를 사전에 확정하기 어렵고, HTTP/1.1에서는 이런 경우 스트리밍 전송을 위해 청크 전송 인코딩이 활용됩니다.

2. Spring Boot SseEmitter의 동작 과정

Spring MVC에서 SseEmitter를 반환하면 일반적으로 다음 흐름으로 동작합니다.

  1. 헤더 설정: Spring이 Content-Type: text/event-stream을 설정
  2. 전송 방식 결정: HTTP/1.1 연결이면 서버(예: Tomcat)가 상황에 따라 Transfer-Encoding: chunked를 사용
  3. 데이터 푸시: emitter.send() 호출 시, SSE 포맷(data: Hello\n\n)으로 구성된 이벤트가 클라이언트로 전송됨
(참고) 실제 “이벤트 하나 = 청크 하나”로 딱 떨어지기보다는, 서버, 컨테이너, 버퍼링 조건에 따라 묶이거나 쪼개질 수 있습니다.
다만 “길이를 모르는 응답을 끊어서 흘려보낸다”는 큰 원리는 동일합니다.

3.1 HTTP 버전에 따른 차이

최신 환경에서는 사용하는 프로토콜에 따라 전송 방식이 달라질 수 있습니다

  • HTTP/1.1: Chunked Transfer Encoding 기반으로 SSE가 동작할 수 있음
  • HTTP/2: Transfer-Encoding: chunked 개념이 없고, HTTP/2 스트림의 DATA 프레임으로 전송됨(헤더에 chunked가 나타나지 않음)
구분 HTTP/1.1 + SSE HTTP/2 + SSE
SSE 포맷 동일 (text/event-stream) 동일
전송 방식 Chunked Transfer Encoding HTTP/2 Stream(Frame)
Content-Length 없음 없음
헤더에 chunked 보임 ❌ 없음
중간 버퍼링 영향 적음
안정성 상대적으로 약함 더 안정적

정리하면, SSE 자체(포맷과 의미)는 동일하지만, HTTP 레벨의 전송 메커니즘이 다르게 동작합니다.

 

HTTP/1.1 SSE 헤더

SSE는 “끝이 정해지지 않은 응답”이므로 Content-Length를 두기 어렵습니다.
따라서 HTTP/1.1에서는 스트리밍 전송을 위해 Transfer-Encoding: chunked가 사용되는 경우가 많습니다.

HTTP/1.1 200 OK
Content-Type: text/event-stream
Transfer-Encoding: chunked
chunk
  data: event 1\n\n  // 이벤트 하나 = 하나의 chunk (보통)

chunk
  data: event 2\n\n

chunk
  data: event 3\n\n

 

HTTP/2 SSE 헤더

HTTP/2에는 chunked 개념이 없기 때문에 Transfer-Encoding: chunked가 나타나지 않습니다.
데이터는 HTTP/2의 프레임(DATA frame) 단위로 스트림을 통해 계속 흘러갑니다.

:status: 200
content-type: text/event-stream 
DATA frame
  data: event 1\n\n // 하나의 요청 = 하나의 Stream

DATA frame
  data: event 2\n\n

HTTP/1.1에서는 SSE 연결이 연결 자원을 상대적으로 더 점유할 수 있지만, HTTP/2에서는 스트림 멀티플렉싱 덕분에 동시 처리에 더 유리합니다.

 

 

HTTP 스트리밍 개념과 흉내내는 방식 그리고 SSE를 살펴봤습니다. HTTP 스트리밍이 무엇인지부터 시작해서, HTTP/1.1에서 스트리밍을 가능하게 해주는 청크 전송 인코딩, 그리고 애플리케이션 레벨에서 실시간 이벤트 전송을 표준화한 SSE까지 흐름으로 정리해봤습니다.

 

핵심은 “SSE는 텍스트 이벤트 포맷이고, 실제 전송은 HTTP 버전에 따라 달라진다”는 점입니다. HTTP/1.1에서는 길이를 알 수 없는 응답을 chunked로 흘려보내고, HTTP/2에서는 같은 SSE 포맷이 DATA frame으로 전달됩니다. SSE를 적용할 때 반드시 부딪히는 주제(SseEmitter 사용 방법, 버퍼링, 타임아웃, 재연결/Last-Event-ID, 연결 수 관리, 로드밸런서/프록시 설정)를 중심으로, Spring에서 안정적으로 운영하는 포인트를 정리해보겠습니다.

'🌎Http' 카테고리의 다른 글

HTTP: HTTP와 TCP 관계  (1) 2026.01.13