Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

On a machine running Sophos, why do all my browsers fail to receive server sent events (sse) from my Python apps in realtime?

My ASGI app sends events fine to curl, and to my phone. However, even though the server is sending the events, and the headers look right, neither Firefox nor Chrome on my Windows machine receives the events until the connection is closed.

This happens whether I host the server in WSL, in Powershell terminal, or on a separate Linux box.

However, those same browsers work fine if I host the server on repl.it (please fork it and try it out).

I have tried fiddling with Windows firewall settings, to no avail.

Here is the application code:

import asyncio
import datetime


async def app(scope, receive, send):
    headers = [(b"content-type", b"text/html")]
    if scope["path"] == "/":
        body = (
            "<html>"
            "<body>"
            "</body>"
            "<script>"
            "  let eventSource = new EventSource('/sse');"
            "  eventSource.addEventListener('message', (e) => {"
            "    document.body.innerHTML += e.data + '<br>';"
            "  });"
            "</script>"
            "</html>"
        ).encode()

        await send({"type": "http.response.start", "status": 200, "headers": headers})
        await send({"type": "http.response.body", "body": body})
    elif scope["path"] == "/sse":
        headers = [
            (b"content-type", b"text/event-stream"),
            (b"cache-control", b"no-cache"),
            (b"connection", b"keep-alive"),
        ]

        async def body():
            ongoing = True
            while ongoing:
                try:
                    payload = datetime.datetime.now()
                    yield f"data: {payload}\n\n".encode()
                    await asyncio.sleep(10)
                except asyncio.CancelledError:
                    ongoing = False

        await send({"type": "http.response.start", "status": 200, "headers": headers})
        async for chunk in body():
            await send({"type": "http.response.body", "body": chunk, "more_body": True})
        await send({"type": "http.response.body", "body": b""})
    else:
        await send({"type": "http.response.start", "status": 404, "headers": headers})
        await send({"type": "http.response.body", "body": b""})

This can be run by naming the file above to asgi_sse.py, then pip install uvicorn, then using something like

uvicorn asgi_sse:app

(substitute daphne or hypercorn instead of uvicorn above to see how those servers handle the app.)

The headers:

$ curl -I http://localhost:8000/sse
HTTP/1.1 200 OK
date: Mon, 01 Jun 2020 09:51:41 GMT
server: uvicorn
content-type: text/event-stream
cache-control: no-cache
connection: keep-alive

And the response:

$ curl http://localhost:8000/sse
data: 2020-06-01 05:52:40.735403

data: 2020-06-01 05:52:50.736378

data: 2020-06-01 05:53:00.736812

Any insights are quite welcome!

like image 900
jdbow75 Avatar asked Sep 04 '25 02:09

jdbow75


1 Answers

I ran into the the same issue recently but with a NodeJS + Express app. I ended up sending a 2 megabyte chunk if the connection is not secure.

if (!req.secure) {
  res.write(new Array(1024 * 1024).fill(0).toString());
}

This is to get server sent events working on a development environment without a secure connection.

The complete implementation:

app.use('/stream', (req, res) => {
  res.set({
    'Content-Type': 'text/event-stream;charset=utf-8',
    'Cache-Control': 'no-cache, no-transform',
    'Content-Encoding': 'none',
    'Connection': 'keep-alive'
  });
  res.flushHeaders();

  if (!req.secure) {
    res.write(new Array(1024 * 1024).fill(0).toString());
  }

  const sendEvent = (event, data) => {
    res.write(`event: ${String(event)}\n`);
    res.write(`data: ${data}`);
    res.write('\n\n');
    res.flushHeaders();
  };

  const intervalId = setInterval(() => {
    sendEvent('ping', new Date().toLocaleTimeString());
  }, 5000);

  req.on('close', () => {
    clearInterval(intervalId);
  });
});
like image 198
Néstor Diaz Avatar answered Sep 06 '25 17:09

Néstor Diaz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!