Server Sent Events are a valuable tool to open a persistent connection to a web server, where the server has the ability to push new data to the client, when available.
Using this technology in Node.js is quite straightforward and can be implemented with the following code example:
#!/usr/bin/env node
'use strict';
const http = (options, listener) => require('http').createServer(listener).listen(options.port);
http({ port: 8080 }, (req, res) => {
switch (req.url) {
case '/server-sent-events': {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Connection': 'keep-alive',
'Cache-Control': 'no-cache',
});
const sendDate = () => res.write(`data: ${new Date()}\n\n`);
sendDate();
const interval = setInterval(sendDate, 1000);
req.on('close', () => clearInterval(interval));
} break;
default: {
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8',
});
res.end(`
<!DOCTYPE html>
<html>
<head>
<title>Server Send Events</title>
<meta charset="utf-8">
<script>
const sse = new EventSource('/server-sent-events');
sse.onerror = () => document.body.innerHTML = 'Connection Error';
sse.onmessage = ({ data }) => document.body.innerHTML = data;
</script>
</head>
<body></body>
</html>
`);
}
}
});
Unfortunately I am not able to achieve the same goal with Deno, as there is no simple write method on the request object, but I guess it has to be implemented somehow using the req.w buffer. Can you help me please finish off the following example code, so the Server Sent Events can be utilised with Deno as well?
#!/usr/bin/env deno run --allow-net
import { listenAndServe as http } from 'https://deno.land/std/http/server.ts';
http({ port: 8080 }, (req) => {
switch (req.url) {
case '/server-sent-events': {
// missing steps:
// * setup the server sent event headers
// * create the interval and send the date periodically
// * clear the interval when the connection gets closed
} break;
default: {
req.respond({
headers: new Headers({
'Content-Type': 'text/html; charset=utf-8',
}),
body: `
<!DOCTYPE html>
<html>
<head>
<title>Server Send Events</title>
<meta charset="utf-8">
<script>
const sse = new EventSource('/server-sent-events');
sse.onerror = () => document.body.innerHTML = 'Connection Error';
sse.onmessage = ({ data }) => document.body.innerHTML = data;
</script>
</head>
<body></body>
</html>
`,
});
}
}
});
Thank you very much for your support!
[Update 2021-11-04]:
I have made some progress doing some research across different sources (https://deno.land/std@0.76.0/http/server.ts, https://github.com/denoland/deno/issues/4817) and got a step closer to the solution. Using the updated example below at least the setup and usage of the Server Sent Events do work now. The remaining issue (besides cleaning up and refactoring of the code) remains the safe detection when the incoming request has been closed (see comments in the source code below):
#!/usr/bin/env deno run --allow-net
import { listenAndServe as http } from 'https://deno.land/std/http/server.ts';
http({ port: 8080 }, (req) => {
switch (req.url) {
case '/server-sent-events': {
// set up a quick´n´dirty write method without error checking
req.write = (data) => {
req.w.write(new TextEncoder().encode(data));
req.w.flush();
};
// setup the server sent event headers
let headers = '';
headers += 'HTTP/1.1 200 OK\r\n';
headers += 'Connection: keep-alive\r\n';
headers += 'Cache-Control: no-cache\r\n';
headers += 'Content-Type: text/event-stream\r\n';
headers += '\r\n';
req.write(headers);
// create the interval and send the date periodically
const sendDate = () => req.write(`data: ${new Date()}\n\n`);
sendDate();
const interval = setInterval(sendDate, 1000);
// final missing step:
// * clear the interval when the connection gets closed
// currently dropping the connection from the client will
// result in the error: Uncaught (in promise) BrokenPipe:
// Broken pipe (os error 32)
// this error also does not seem to be catchable in the
// req.write method above, so there needs to be another safe
// way to prevent this error from occurring.
} break;
default: {
req.respond({
headers: new Headers({
'Content-Type': 'text/html; charset=utf-8',
}),
body: `
<!DOCTYPE html>
<html>
<head>
<title>Server Send Events</title>
<meta charset="utf-8">
<script>
const sse = new EventSource('/server-sent-events');
sse.onerror = () => document.body.innerHTML = 'Connection Error';
sse.onmessage = ({ data }) => document.body.innerHTML = data;
</script>
</head>
<body></body>
</html>
`,
});
}
}
});
[Update 2021-04-16]
All issues have been resolved and are posted in my accepted answer below.