--- title: Server-Sent Events 사용하기 slug: Web/API/Server-sent_events/Using_server-sent_events tags: - Advanced - Communication - DOM - Guide - SSE - Server Sent Events - Server-sent events - messaging ---
{{DefaultAPISidebar("Server Sent Events")}}
Server-Sent Events를 사용하는 웹 애플리케이션 개발은 매우 간단하다. 웹 애플리케이션으로 스트림 이벤트를 보내는 약간의 코드가 서버 상에 필요하지만, 웹 애플리케이션 측은 웹 소켓에서 이벤트를 다루는 방식과 거의 차이가 없다.
Server-Sent Event API는 EventSource
인터페이스에 포함돼 있다. 이벤트를 전달 받기 위해서 서버로 접속을 시작하려면 우선, 이벤트를 생성하는 서버측 스크립트를 URI로 지정하여 새로운 EventSource
객체를 생성한다. 예를 들어:
var evtSource = new EventSource("ssedemo.php");
이벤트를 생성하는 스크립트가 다른 도메인에 존재할 경우엔 URI와 옵션 딕셔너리를 모두 지정하여 새로운 EventSource
객체를 생성한다. 클라이언트 스크립트가 example.com에 있는 경우라면:
var evtSource = new EventSource("//api.example.com/ssedemo.php", { withCredentials: true } );
이벤트 소스를 생성 했다면 message
이벤트에 대한 핸들러를 등록하여 서버로부터의 메시지 수신을 시작할 수 있다.
evtSource.onmessage = function(e) { var newElement = document.createElement("li"); var eventList = document.getElementById('list'); newElement.innerHTML = "message: " + e.data; eventList.appendChild(newElement); }
위 코드는 입력 메시지(즉, event
필드를 갖고 있지 않은 서버로부터의 알림)를 수신하여 그 메시지의 텍스트를 document의 HTML에 있는 목록에 추가한다.
또는 addEventListener()
를 사용하여 이벤트를 기다릴 수도 있다.
evtSource.addEventListener("ping", function(event) { const newElement = document.createElement("li"); const time = JSON.parse(event.data).time; newElement.textContent = "ping at " + time; eventList.appendChild(newElement); });
앞서 소개한 코드와 비슷하지만 event
필드에 "ping"이 설정된 메시지가 서버로부터 보내졌을 때만 자동으로 호출된다는 점이 다르다.
When not used over HTTP/2, SSE suffers from a limitation to the maximum number of open connections, which can be especially painful when opening multiple tabs, as the limit is per browser and is set to a very low number (6). The issue has been marked as "Won't fix" in Chrome and Firefox. This limit is per browser + domain, which means that you can open 6 SSE connections across all of the tabs to www.example1.com
and another 6 SSE connections to www.example2.com
(per Stackoverflow). When using HTTP/2, the maximum number of simultaneous HTTP streams is negotiated between the server and the client (defaults to 100).
이벤트를 송신하는 서버 사이드의 스크립트는 MIME 타입 text/event-stream
을 사용해 응답할 필요가 있다. 각 알림은 두 개의 줄 바꿈으로 끝나는 텍스트 블럭으로 전송된다. 이벤트 스트림의 형식에 관한 자세한 내용은 {{ anch("Event stream format") }}을 참고한다.
다음은 우리가 사용하고 있는 PHP 코드 예다.
date_default_timezone_set("America/New_York"); header("Content-Type: text/event-stream\n\n"); $counter = rand(1, 10); while (1) { // "ping" 이벤트를 초당 송신 echo "event: ping\n"; $curDate = date(DATE_ISO8601); echo 'data: {"time": "' . $curDate . '"}'; echo "\n\n"; // 간단한 메시지를 랜덤 간격으로 송신 $counter--; if (!$counter) { echo 'data: This is a message at time ' . $curDate . "\n\n"; $counter = rand(1, 10); } ob_end_flush(); flush(); sleep(1); }
위 코드는 이벤트 타입이 "ping"인 이벤트를 초당 생성한다. 각 이벤트 데이터는 이벤트가 생성된 시각의 ISO 8601 형식의 타입스탬프를 포함하는 JSON 객체다. 또, 랜덤한 간격으로 간단한 메시지(이벤트타입 없는)를 송신한다.
The loop will keep running independent of the connection status, so a check is included
to break the loop if the connection has been closed (e.g. client closes the page).
문제가 발생한 경우(네크워크 타임아웃이나 접근 제약과 관련한 문제)에는 오류 이벤트를 생성한다. EventSource
갹채에 onerror
콜백을 등록하면 에러를 프로그램으로 대처할 수 있다.
evtSource.onerror = function(e) { alert("EventSource failed."); };
기본적으로는 클라이언트와 서버 사이의 연결이 닫히면 연결이 재시작된다. 연결은 .close()
메서드로 종료한다.
evtSource.close();
이벤트 스트림은 텍스트 데이터의 단순한 스트림으로 UTF-8을 사용하여 인코딩 해야만 한다. 이벤트 스트림 내부 메시지는 두 개의 줄바꿈 문자로 구분된다. 행 선두에 있는 콜론은 본질적으로 주석으로 나타내며 무시된다.
각 메시지는 필드를 나열한 하나 이상의 텍스트 행으로 구성된다. 각 필드는 "필드명, 그 다음 콜론, 이어서 필드의 값에 해당하는 텍스트 데이터"로 나타낸다.
다음과 같은 필드명이 사양에 정리돼 있다.
event
addEventListener()
를 사용한다. 메시지에서 이벤트 명이 지정되 있지 않은 경우는 onmessage
핸들러가 호출된다.data
EventSource
가 data:
로 시작된다. 복수의 연속된 행을 전달 받은 경우에는 그것을 연결해 각 항목의 사이에 개행 문자를 삽입한다. 이때, 마지막의 줄바꿈은 제외된다.id
EventSource
가 data:로 시작된다. 복수의 연속된 행을 전달 받은 경우에는 그것을 연결해 각 항목의 사이에 개행 문자를 삽입한다. 이때, 마지막의 줄바꿈은 제외된다.retry
이 필드명 외의 다른 필드는 모두 무시된다.
아래 예에서는 세 개의 메시지가 송신되고 있다. 최초의 메시지는 콜론 문자로 시작되고 있다. 주석이다. 앞서 설명한 바와 같이 코멘트는 메시지가 정기적으로 송신되지 않을 가능성이 있을 경우 킵얼라이브 용으로 사용할 수 있다.
두 번째 메시지는 값이 "some text"인 data 필드를 갖고있다. 세 번째 메시지는 값이 "another message\nwith two lines"인 data 필드를 갖고 있다. 값에 줄 바꿈 문자가 있음을 주의하라.
: this is a test stream data: some text data: another message data: with two lines
아래 예에서는 이름이 있는 이벤트를 몇개 송신하고 있다. 각각의 이벤트는 event
필드로 지정된 이벤트 명을 갖고 있고 또, 클라이언트에서 필요한 데이터를 포함하는 적절한 JSON 문자열을 값으로 갖는 data
필드도 있다. 물론 data
필드는 임의의 문자열 데이터를 가질 수 있다. 꼭 JSON 일 필요는 없다.
event: userconnect data: {"username": "bobby", "time": "02:33:48"} event: usermessage data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."} event: userdisconnect data: {"username": "bobby", "time": "02:34:23"} event: usermessage data: {"username": "sean", "time": "02:34:36", "text": "Bye, bobby."}
이름 없는 메시지 또는, 이름이 있는 이벤트만 사용해야 하는 경우는 없다. 그리고 이것을 하나의 이벤트 스트림 내에서 혼합해 표현할 수 있다.
event: userconnect data: {"username": "bobby", "time": "02:33:48"} data: Here's a system message of some kind that will get used data: to accomplish some task. event: usermessage data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}
EventSource
{{Compat("api.EventSource")}}