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

Reply via email to