|
| 1 | +# Server Sent Events |
| 2 | + |
| 3 | +La specifica [Server-Sent Events](https://html.spec.whatwg.org/multipage/comms.html#the-eventsource-interface) descrive una classe built-in `EventSource`, che mantiene la connessione con il server e permette di ricevere eventi da esso. |
| 4 | + |
| 5 | +In modo simile ai `WebSocket`, la connessione è persistente. |
| 6 | + |
| 7 | +Ci sono però delle differenze sostanziali: |
| 8 | + |
| 9 | +| `WebSocket` | `EventSource` | |
| 10 | +|-------------|---------------| |
| 11 | +| Bidirezionale: sia il client che il server possono scambiare messaggi | Unidirezionale: solamente il server può inviare messaggi | |
| 12 | +| Dati binari e testuali | Solo testuali | |
| 13 | +| Protocollo WebSocket | HTTP standard | |
| 14 | + |
| 15 | +`EventSource` è un modo meno potente di comunicare con il server rispetto ai `WebSocket`. |
| 16 | + |
| 17 | +Perché dovremmo usarli? |
| 18 | + |
| 19 | +La ragione principale: è semplice da usare. In molte applicazioni, la potenza dei `WebSocket` è anche troppa. |
| 20 | + |
| 21 | +Se abbiamo necessità di ricevere un flusso di dati da un server: che siano messaggi di chat o variazioni di prezzo dei mercati. Allora è ciò per cui `EventSource` è fatto. Supporta anche l'auto riconnessione, la qualcosa dovremmo invece implementare manualmente nei `WebSocket`. Oltretutto, è un normalissimo HTTP, e non un nuovo protocollo. |
| 22 | + |
| 23 | +## Ottenere i messaggi |
| 24 | + |
| 25 | +Per cominciare a ricevere messaggi, dobbiamo solamente creare un `new EventSource(url)`. |
| 26 | + |
| 27 | +Il browser si connetterà all'url e terrà la connessione aperta, in attesa di eventi. |
| 28 | + |
| 29 | +Il server dovrebbe rispondere con status 200 ed header `Content-Type: text/event-stream`, dopodiché mantenere aperta la connessione e scrivere i messaggi all'interno di esso in un formato speciale del tipo: |
| 30 | + |
| 31 | +``` |
| 32 | +data: Message 1 |
| 33 | +
|
| 34 | +data: Message 2 |
| 35 | +
|
| 36 | +data: Message 3 |
| 37 | +data: of two lines |
| 38 | +``` |
| 39 | + |
| 40 | +- Un messaggio di testo segue la stringa `data:`, lo spazio dopo la virgola è opzionale. |
| 41 | +- I messaggi sono delimitati con un doppio line break `\n\n`. |
| 42 | +- Per inviare un line break `\n`, possiamo inviare immediatamente un altro `data:` (il terzo messaggio nell'esempio precedente). |
| 43 | + |
| 44 | +In pratica, i messaggi complessi sono solitamente inviati tramite oggetti codificati in JSO. I Line-breaks sono codificati come `\n`, e in questo modo i messaggi `data:` multiriga non sono necessari |
| 45 | + |
| 46 | +Ad esempio: |
| 47 | + |
| 48 | +```js |
| 49 | +data: {"user":"John","message":"First line*!*\n*/!* Second line"} |
| 50 | +``` |
| 51 | + |
| 52 | +...In questo modo possiamo assumere che ogni `data` contenga esattamente un messaggio. |
| 53 | + |
| 54 | +Per ognuno di questi messaggi, viene generato l'evento `message`: |
| 55 | + |
| 56 | +```js |
| 57 | +let eventSource = new EventSource("/events/subscribe"); |
| 58 | + |
| 59 | +eventSource.onmessage = function(event) { |
| 60 | + console.log("New message", event.data); |
| 61 | + //logghera' 3 volte per il data stream poco sopra |
| 62 | +}; |
| 63 | + |
| 64 | +// oppure eventSource.addEventListener('message', ...) |
| 65 | +``` |
| 66 | + |
| 67 | +### Richieste Cross-origin |
| 68 | + |
| 69 | +`EventSource` supporta le richieste cross-origin, come `fetch` e qualunque altro metodo di rete. Possiamo usare qualunque URL: |
| 70 | + |
| 71 | +```js |
| 72 | +let source = new EventSource("https://another-site.com/events"); |
| 73 | +``` |
| 74 | +Il server remoto otterrà l'header `Origin` e dovrà rispondere con `Access-Control-Allow-Origin` per continuare. |
| 75 | + |
| 76 | +Per inviare credenziali, dovremmo impostare le opzioni aggiuntive `withCredentials`, in questo modo: |
| 77 | + |
| 78 | +```js |
| 79 | +let source = new EventSource("https://another-site.com/events", { |
| 80 | + withCredentials: true |
| 81 | +}); |
| 82 | +``` |
| 83 | + |
| 84 | +Si prega di guardare il capitolo <info:fetch-crossorigin> per maggiori informazioni sugli headers cross-origin. |
| 85 | + |
| 86 | + |
| 87 | +## Riconnessione |
| 88 | + |
| 89 | +In fase di creazione, `new EventSource` si connette al server, e se la connessione si interrompe -- si riconnette. |
| 90 | + |
| 91 | +Ciò è molto conveniente, dal momento che non ci dobbiamo curare della cosa. |
| 92 | + |
| 93 | +C'è un piccolo ritardo tra le riconnessioni, pochi secondi di default. |
| 94 | + |
| 95 | +Il server può impostare il ritardo raccomandato usando `retry:` nella risposta (in millisecondi) |
| 96 | + |
| 97 | +```js |
| 98 | +retry: 15000 |
| 99 | +data: Hello, I set the reconnection delay to 15 seconds |
| 100 | +``` |
| 101 | + |
| 102 | +Il `retry:` può arrivare insieme ad altri dati, o come messaggio singolo. |
| 103 | + |
| 104 | +Il browser dovrebbe attendere questi millisecondi prima di riconnettersi. O anche di più, ad esempio se il browser sa (dall'OS) che non c'è connessione in quel momento, può attendere fino a quando la connessione non ritorna, e successivamente riprovare. |
| 105 | + |
| 106 | +- Se il server vuole che il browser smetta di riconnettersi, dovrebbe rispondere con uno status HTTP 204. |
| 107 | +- Se il browser vuole chiudere la connessione, dovrebbe chiamare il metodo `eventSource.close()`: |
| 108 | + |
| 109 | +```js |
| 110 | +let eventSource = new EventSource(...); |
| 111 | + |
| 112 | +eventSource.close(); |
| 113 | +``` |
| 114 | +Inoltre, non avverrà alcuna riconnessione se la risposta ha un `Content-type` non valido o se il suo HTTP status è diverso da 301, 307, 200 o 204. In questi casi verrà emesso l'evento `"error"`, e il browser non si riconnetterà. |
| 115 | + |
| 116 | +```smart |
| 117 | +Quando una connessione è finalemente chiusa, non ci sarà modo di "riaprirla". Se volessimo riconnetterci nuovamente, dovremmo ricreare un nuovo `EventSource`. |
| 118 | +``` |
| 119 | + |
| 120 | +## Message id |
| 121 | + |
| 122 | +Quando una connessione si interrompe per motivi di problemi di rete, ogni lato non può essere sicuro di quale messaggi siano stati ricevuti, e quali no. |
| 123 | +Per riprendere correttamente la connessione, ogni messaggio dovrebbe avere un campo `id`, come questo: |
| 124 | +``` |
| 125 | +data: Message 1 |
| 126 | +id: 1 |
| 127 | +
|
| 128 | +data: Message 2 |
| 129 | +id: 2 |
| 130 | +
|
| 131 | +data: Message 3 |
| 132 | +data: of two lines |
| 133 | +id: 3 |
| 134 | +``` |
| 135 | +Quando viene ricevuto un messaggio con `id:`, il browser: |
| 136 | + |
| 137 | +- Imposta la proprietà `eventSource.lastEventId` su quel valore. |
| 138 | +- In fase di riconnessione invia l'header `Last-Event-ID` con quell'`id`, in modo da permettere al server di reinviare i messaggi successivi. |
| 139 | + |
| 140 | +```smart header="Inserisci `id:` dopo `data:`" |
| 141 | +Nota bene: l'`id` viene aggiunto dopo il messaggio `data` dal server, per assicurarsi che `lastEventId` venga aggiornato solamente dopo che il messaggio sia stato ricevuto. |
| 142 | +``` |
| 143 | +
|
| 144 | +## Stato della conessione: readyState |
| 145 | +
|
| 146 | +L'oggetto `EventSource` possiede la proprietà `readyState`, che può assumere uno dei seguenti valori: |
| 147 | +
|
| 148 | +```js no-beautify |
| 149 | +EventSource.CONNECTING = 0; // connessione o riconnessione |
| 150 | +EventSource.OPEN = 1; // connesso |
| 151 | +EventSource.CLOSED = 2; // connessione chiusa |
| 152 | +``` |
| 153 | + |
| 154 | +Quando viene creato un oggetto, o se la connessione è assente, viene valorizzato sempre a `EventSource.CONNECTING` (equivale a `0`). |
| 155 | + |
| 156 | +Possiamo interrogare questa proprietà per sapere lo stato di `EventSource`. |
| 157 | + |
| 158 | +## Tipi di evento |
| 159 | + |
| 160 | +Di base l'oggetto `EventSource` genera tre eventi: |
| 161 | + |
| 162 | +- `message` -- un messaggio ricevuto, disponibile come `event.data`. |
| 163 | +- `open` -- la connessione è aperta. |
| 164 | +- `error` -- la connessaione non può essere stabilita, ad esempio, il server ha risposto con lo status HTTP 500. |
| 165 | + |
| 166 | +Il server può specificare un altro tipo di evento con `event: ...` all'inizio dell'evento. |
| 167 | + |
| 168 | +Per esempio: |
| 169 | + |
| 170 | +``` |
| 171 | +event: join |
| 172 | +data: Bob |
| 173 | +
|
| 174 | +data: Hello |
| 175 | +
|
| 176 | +event: leave |
| 177 | +data: Bob |
| 178 | +``` |
| 179 | + |
| 180 | +Per gestire eventi custom, dobbiamo usare `addEventListener`, e non `onmessage`: |
| 181 | + |
| 182 | +```js |
| 183 | +eventSource.addEventListener('join', event => { |
| 184 | + alert(`Joined ${event.data}`); |
| 185 | +}); |
| 186 | + |
| 187 | +eventSource.addEventListener('message', event => { |
| 188 | + alert(`Said: ${event.data}`); |
| 189 | +}); |
| 190 | + |
| 191 | +eventSource.addEventListener('leave', event => { |
| 192 | + alert(`Left ${event.data}`); |
| 193 | +}); |
| 194 | +``` |
| 195 | + |
| 196 | +## Esempio completo |
| 197 | + |
| 198 | +Qui c'è il server che invia messaggi con `1`, `2`, `3`, ed infine `bye` interrompendo la connessione. |
| 199 | + |
| 200 | +Dopo il browser si riconnette automaticamente. |
| 201 | + |
| 202 | +[codetabs src="eventsource"] |
| 203 | + |
| 204 | +## Riepilogo |
| 205 | + |
| 206 | +L'oggetto `EventSource` stabilisce automaticamente una connessione persistente e permette al server di inviare dei messaggi attraverso di essa. |
| 207 | + |
| 208 | +Offrendo: |
| 209 | +- Riconnessione automatica, con timeout di `retry` regolabili. |
| 210 | +- Id dei messaggi per riprendere gli eventi, l'ultimo id ricevuto viene inviato nell'header `Last-Event-ID` in fase di riconnessione. |
| 211 | +- Lo stato corrente è dentro la proprietà `readyState`. |
| 212 | + |
| 213 | +Ciò rende `EventSource` una valida alternativa ai `WebSocket`, il quale è più a basso livello e manca di alcune funzionalità built-in (sebbene possano essere implementate). |
| 214 | + |
| 215 | +In molte applicazioni reali, la potenza di `EventSource` è già sufficiente. |
| 216 | + |
| 217 | +Supportato in tutti i browser moderni (non IE). |
| 218 | + |
| 219 | +La sintassi è: |
| 220 | + |
| 221 | +```js |
| 222 | +let source = new EventSource(url, [credentials]); |
| 223 | +``` |
| 224 | + |
| 225 | +Il secondo argomento consta di una sola opzione possibile: `{ withCredentials: true }`, la quale permette di inviare credenziali cross-origin. |
| 226 | + |
| 227 | +Complessivamente la sicurezza del cross-origin è la stessa di `fetch` e altri metodi di rete. |
| 228 | + |
| 229 | +### Proprietà di un oggetto `EventSource` |
| 230 | + |
| 231 | +`readyState` |
| 232 | +: Lo stato corrente della connessione: uno tra `EventSource.CONNECTING (=0)`, `EventSource.OPEN (=1)` o `EventSource.CLOSED (=2)`. |
| 233 | + |
| 234 | +`lastEventId` |
| 235 | +: L'ultimo `id` ricevuto.In fase di riconnessione il browser lo invia nell'header `Last-Event-ID`. |
| 236 | + |
| 237 | +### Metodi |
| 238 | + |
| 239 | +`close()` |
| 240 | +: Chiude la connessione. |
| 241 | + |
| 242 | +### Eventi |
| 243 | + |
| 244 | +`message` |
| 245 | +: Messagio ricevuto, il dato è dentro `event.data`. |
| 246 | + |
| 247 | +`open` |
| 248 | +: La connessione è stabilita. |
| 249 | + |
| 250 | +`error` |
| 251 | +: In caso di errori, inclusi la connessione persa (con riconnessione automatica) ed errori fatali. Possiamo controllare `readyState` per vedere se è stata tentata la riconnessione. |
| 252 | + |
| 253 | +Il server può impostare un evento personalizzato dentro `event:`. Questi eventi andrebbero gestiti usando `addEventListener`, e non `on<event>`. |
| 254 | + |
| 255 | +### Formato della risposta del server |
| 256 | + |
| 257 | +Il server invia messaggi, delimitati da `\n\n`. |
| 258 | + |
| 259 | +Un messaggio può avere i seguenti campi: |
| 260 | + |
| 261 | +- `data:` -- corpo del messaggio, una sequenza di `data` multipli viene interpretata come un messaggio singolo, con `\n` tra la parti. |
| 262 | +- `id:` -- aggiorna `lastEventId`, inviato dentro `Last-Event-ID` in fase di riconnessione. |
| 263 | +- `retry:` -- raccomanda una ritardo nel tentativo di riconessione in millisecondi. Non c'è modo di impostarlo da JavaScript. |
| 264 | +- `event:` -- event name, must precede `data:`. |
| 265 | + |
| 266 | +Un messaggio può includere uno o più campi in qualunque ordine, ma l'`id:` solitamente va per ultimo. |
0 commit comments