首页 > 解决方案 > 在运行 Sophos 的机器上,为什么我的所有浏览器都无法从我的 Python 应用程序实时接收服务器发送的事件 (sse)?

问题描述

我的 ASGI 应用程序可以很好地向 curl 和我的手机发送事件。但是,即使服务器正在发送事件,并且标头看起来正确,但我的 Windows 机器上的 Firefox 和 Chrome 都不会收到事件,直到连接关闭。

无论我将服务器托管在 WSL、Powershell 终端还是单独的 Linux 机器上,都会发生这种情况。

但是,如果我在 repl.it 上托管服务器,这些相同的浏览器可以正常工作(请分叉并试用)。

我曾尝试摆弄 Windows 防火墙设置,但无济于事。

这是应用程序代码:

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""})

这可以通过将上面的文件命名为asgi_sse.py, 然后pip install uvicorn, 然后使用类似的东西来运行

uvicorn asgi_sse:app

(替换daphnehypercorn代替uvicorn上面的内容,看看这些服务器如何处理应用程序。)

标题:

$ 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

和回应:

$ 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

任何见解都非常受欢迎!

标签: pythonpython-asyncioserver-sent-eventsantivirusasgi

解决方案


我最近遇到了同样的问题,但使用的是 NodeJS + Express 应用程序。如果连接不安全,我最终会发送一个 2 兆字节的块。

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

这是为了让服务器发送的事件在没有安全连接的情况下在开发环境中工作。

完整的实现:

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);
  });
});

推荐阅读