Server-Sent Events 사용하기
=======================

Server-Sent Events를 사용하는 웹 애플리케이션 개발은 매우 간단하다. 웹 애플리케이션으로 스트림 이벤트를 보내는 약간의 코드가 서버 상에 필요하지만, 웹 애플리케이션 측은 웹 소켓에서 이벤트를 다루는 방식과 거의 차이가 없다.

서버에서 이벤트 받기
-----------

Server-Sent Event API는 `EventSource` (en-US) 인터페이스에 포함돼 있다. 이벤트를 전달 받기 위해서 서버로 접속을 시작하려면 우선, 이벤트를 생성하는 서버측 스크립트를 URI로 지정하여 새로운 `EventSource` (en-US) 객체를 생성한다. 예를 들어:


```js
var evtSource = new EventSource("ssedemo.php");
```


이벤트를 생성하는 스크립트가 다른 도메인에 존재할 경우엔 URI와 옵션 딕셔너리를 모두 지정하여 새로운 `EventSource` (en-US) 객체를 생성한다. 클라이언트 스크립트가 example.com에 있는 경우라면:


```js
var evtSource = new EventSource("//api.example.com/ssedemo.php", {
  withCredentials: true,
});
```


이벤트 소스를 생성 했다면 `message` 이벤트에 대한 핸들러를 등록하여 서버로부터의 메시지 수신을 시작할 수 있다.


```js
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()`를 사용하여 이벤트를 기다릴 수도 있다.


```js
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"이 설정된 메시지가 서버로부터 보내졌을 때만 자동으로 호출된다는 점이 다르다.

> 
> **Warning:** 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`을 사용해 응답할 필요가 있다. 각 알림은 두 개의 줄 바꿈으로 끝나는 텍스트 블럭으로 전송된다. 이벤트 스트림의 형식에 관한 자세한 내용은 Event stream format을 참고한다.

다음은 우리가 사용하고 있는 PHP 코드 예다.


```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).

에러 핸들링
------

문제가 발생한 경우(네크워크 타임아웃이나 접근 제약 (en-US)과 관련한 문제)에는 오류 이벤트를 생성한다. `EventSource` 갹채에 `onerror` 콜백을 등록하면 에러를 프로그램으로 대처할 수 있다.


```js
evtSource.onerror = function (e) {
  alert("EventSource failed.");
};
```

이벤트 스트림 닫기
----------

기본적으로는 클라이언트와 서버 사이의 연결이 닫히면 연결이 재시작된다. 연결은 `.close()` 메서드로 종료한다.


```js
evtSource.close();
```

이벤트 스트림 형식
----------

이벤트 스트림은 텍스트 데이터의 단순한 스트림으로 UTF-8을 사용하여 인코딩 해야만 한다. 이벤트 스트림 내부 메시지는 두 개의 줄바꿈 문자로 구분된다. 행 선두에 있는 콜론은 본질적으로 주석으로 나타내며 무시된다.

**참고:** **노트:** 주석 행은 연결이 타임아웃 되는 것을 방지하기 위해 사용할 수 있다. 서버는 연결을 유지하기 위해 정기적으로 주석을 송신할 수 있다.

각 메시지는 필드를 나열한 하나 이상의 텍스트 행으로 구성된다. 각 필드는 "필드명, 그 다음 콜론, 이어서 필드의 값에 해당하는 텍스트 데이터"로 나타낸다.

### 필드

다음과 같은 필드명이 사양에 정리돼 있다.

`event`

이벤트 타입이다. 이 필드가 지정돼 있는 경우, 이벤트는 브라우저 내에서 이벤트명에 해당하는 이벤트 리스너로 전달된다. 웹 사이트의 소스 코드에서는 이름이 붙여진 이벤트를 받기 위해서 `addEventListener()`를 사용한다. 메시지에서 이벤트 명이 지정되 있지 않은 경우는 `onmessage` 핸들러가 호출된다.

`data`

메시지의 데이터 필드다. `EventSource`가 `data:`로 시작된다. 복수의 연속된 행을 전달 받은 경우에는 그것을 연결해 각 항목의 사이에 개행 문자를 삽입한다. 이때, 마지막의 줄바꿈은 제외된다.

`id`

메시지의 데이터 필드다. `EventSource`가 data:로 시작된다. 복수의 연속된 행을 전달 받은 경우에는 그것을 연결해 각 항목의 사이에 개행 문자를 삽입한다. 이때, 마지막의 줄바꿈은 제외된다.

`retry`

이벤트 송신을 시도할 때에 사용하는 재연결 시간(reconnection time)이다. 이 값은 정수여야 하며 재연결 시간을 밀리초 단위로 지정한다. 정수가 아닌 값이 지정되면 이 필드는 무시된다.

이 필드명 외의 다른 필드는 모두 무시된다.

**참고:** **노트:** 행에 콜론이 포함되지 않으면 행 전체가 필드명으로 인식되며 값은 빈문자열로 취급한다.

### 예

#### 데이터만 있는 메시지

아래 예에서는 세 개의 메시지가 송신되고 있다. 최초의 메시지는 콜론 문자로 시작되고 있다. 주석이다. 앞서 설명한 바와 같이 코멘트는 메시지가 정기적으로 송신되지 않을 가능성이 있을 경우 킵얼라이브 용으로 사용할 수 있다.

두 번째 메시지는 값이 "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."}
```

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.