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?
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.
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.
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.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With