Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to close a "Server-Sent Events"-connection on the server?

Regarding this specification: http://www.w3.org/TR/eventsource/

How does one close the opened connection on the server? Client-side wise it is easy, just call close(), but what should I do on the server? Just kill it?

like image 992
Tower Avatar asked Jun 30 '11 12:06

Tower


People also ask

How do you close an event source?

close() The close() method of the EventSource interface closes the connection, if one is made, and sets the EventSource. readyState attribute to 2 (closed). Note: If the connection is already closed, the method does nothing.

How do you stop responding to events in a stream?

Cancel an event stream # To cancel a stream from the server, respond with a non text/event-stream Content-Type or return an HTTP status other than 200 OK (e.g. 404 Not Found ). Both methods will prevent the browser from re-establishing the connection.

How do server-sent events work?

Server-Sent Events is designed to use the JavaScript EventSource API in order to subscribe to a stream of data in any popular browser. Through this interface a client requests a particular URL in order to receive an event stream.

What are the events available for server-sent events?

Event typesmessage – a message received, available as event. data . open – the connection is open. error – the connection could not be established, e.g. the server returned HTTP 500 status.


2 Answers

So, I've searched around for a solution built into the protocol, and it there does not appear to be one. If your server calls response.emit('close') or response.end(), the client will treat this like an error and attempt to reconnect to the server. (At least in the case of Chrome, it will attempt to reconnect indefinitely, unless it considers the network error fatal).

So it appears that one way or another, your client has to close the connection. That leaves two options. The first, is to simply assume any error from the server should close the EventSource.

const sse = new EventSource('/events')
sse.onmessage = m => console.log(m.data)
sse.onerror = () => sse.close()

The above leaves a few things to be desired though. We are assuming that a network error is a graceful shutdown, this may not be the case. There may also be some scenarios where we do want the reconnect behavior.

So instead, why don't we just ask the client to gracefully shutdown itself! We have a way to send messages to the client from the server, so all we need to do is send a message from the server that says "shut me down".

// client.js
const maxReconnectTries = 3

let reconnectAttempts = 0
const sse = new EventSource('/events')
sse.onmessage = m => {
  const { type, data } = JSON.parse(m.data)
  if (type === 'close') sse.close()
  else console.log(data)
}
sse.onerror = () => {
  if (reconnectAttempts > maxReconnectTries) {
    sse.close()
    alert("We have a baaad network error!")
  } else {
    reconnectAttempts++
  }
}
// server.js
const express = require('express')


function sendEvent(res, type, data) {
  res.write(`data: ${JSON.stringify({ type, data })}\n\n`)
}

function sseHandler(req, res) {
  response.writeHead(200, {
    'Connection': 'keep-alive',
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache'
  }

  let manualShutdown
  request.on('close', () => {
    console.log('disconnected.')
    clearTimeout(manualShutdown)  // prevent shutting down the connection twice
  })
  
  sendEvent(res, 'message', `Ping sent at ${new Date()}`)

  // when it comes time to shutdown the event stream (for one reason or another)
  setTimeout(() => {
    sendEvent(res, 'close', null)
    // give it a safe buffer of time before we shut it down manually
    manualShutdown = setTimeout(() => res.end(), clientShutdownTimeout)
  }, 10000)
}

const clientShutdownTimeout = 2000
const app = express()
app.get('/events', sseHandler)
app.listen(4000, () => console.log('server started on 4000'))

This covers all the areas that we need for a safe client/server implementation. If there's an issue on the server, we attempt to reconnect but can still notify the client if there's a failure. When the server wants the connection closed, it asks the client to close the connection. After two seconds, if the client has not shut down the connection we can assume something went wrong and close the connection server side.

What we are doing here is building a protocol on top of server sent events. It has a very simple api: { "type": "close" } tells the client to close the server, and { "type": "message", "data": {"some": "data" } tells the client this is a regular message.

like image 89
andykais Avatar answered Oct 04 '22 22:10

andykais


node.js:

http.createServer(function (req, res) {

    //...

    // client closes connection
    res.socket.on('close', function () {
      res.end();
      //...
    });

});

see example for implementation of SSE server in node.js:

https://github.com/Yaffle/EventSource/blob/master/nodechat/server.js

like image 24
4esn0k Avatar answered Oct 04 '22 21:10

4esn0k