I found a way that works to detect when the client closes the browser and thus
close the connection (based on @alexeypetrushin idea of sending a ping every n
sec to the server and [Using server-sent events Mozilla's
documentation](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)).
import httpcore, asyncdispatch, strformat, asynchttpserver, asyncnet
import times
from oids import genOid, `$`
from md5 import toMD5, `$`
from strutils import split
import tables
const
pingTime = 10 # 10 seconds
timeout = 30 # 30 seconds
var testClients = newTable[string, float]()
proc ping(req: Request): Future[void] {.async,gcsafe.} =
let pair = req.url.query.split('=')
echo "Pair: ", $pair
if (pair.len == 2) and (pair[0] == "key") and (pair[1] in testClients):
testClients.del(pair[1])
await req.respond(Http200, "")
proc ssedemo(req: Request): Future[void] {.async,gcsafe.} =
let headers = newHttpHeaders(@[
("Content-Type", "text/event-stream"),
("Cache-Control", "no-cache"),
("Connection", "keep-alive"),
("Access-Control-Allow-Origin", "*"),
("Content-Length", "")
])
await req.respond(Http200, "200", headers)
var savedTime = epochTime()
var key = ""
var i = 0
while not req.client.isclosed():
if (key != "") and (key in testClients):
if (epochTime() - testClients[key]) > timeout:
testClients.del(key)
break
else:
key = ""
echo "I'm alive ", $i
var msg = "event: test\ndata: " & $i & "\n\n"
let elapsed = epochTime() - savedTime
if key == "" and (elapsed >= pingTime):
key = $toMD5($genOid())
savedTime = epochTime()
testClients[key] = savedTime
msg.add("event: ping\ndata: " & key & "\n\n")
await req.respond(Http200, msg)
await sleep_async(1000)
inc(i)
echo "client disconnected"
req.client.close()
proc home(req: Request): Future[void] {.async.} =
const homePage = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Test SSE</title>
</head>
<body>
<div id="testdemo"></div>
<div id="testping"></div>
<script type="text/javascript">
//<![CDATA[
if(typeof(EventSource) !== "undefined") {
// Yes! Server-sent events support!
const evtSource = new EventSource("ssedemo");
evtSource.onopen = function (event) {
console.log("EventSource opened");
}
// evtSource.onmessage = function(event) {
// console.log("New Message...");
// console.log("Data: " + event.data);
//
// document.getElementById('demotest').innerHTML = "message: " +
event.data;
// }
evtSource.addEventListener("test", function(event) {
console.log("New Test");
console.log("Data: " + event.data);
document.getElementById('testdemo').innerHTML = "message: " +
event.data;
});
evtSource.addEventListener("ping", function(event) {
console.log("New Ping");
console.log("Ping Data: " + event.data);
document.getElementById('testping').innerHTML = "ping data: " +
event.data;
const xhttp = new XMLHttpRequest();
xhttp.open("GET", "ping?key=" + event.data, true);
xhttp.send();
});
evtSource.onerror = function(err) {
console.error("EventSource failed:", err);
};
} else {
// Sorry! No server-sent events support..
console.log("Sorry! No server-sent events support..");
}
//]]>
</script>
</body>
</html>"""
await req.respond(Http200, homePage)
proc cb(req: Request): Future[void] {.async.} =
if req.reqMethod == HttpGet:
if req.url.path == "/":
await req.home()
elif req.url.path == "/ssedemo":
await req.ssedemo()
elif req.url.path == "/ping":
await req.ping()
else:
await req.respond(Http200, "Unknown")
else:
await req.respond(Http200, "Unknown")
var server = new_async_http_server()
async_check server.serve(Port(5000), cb, "localhost")
echo "started"
run_forever()
Run