在一台运行Sophos的机器上,为什么我所有的浏览器都无法实时接收到来自Python应用的服务器发送的事件(sse)?

问题描述 投票:0回答:1

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

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

然而,如果我将服务器托管在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

欢迎提供任何见解!

python python-asyncio server-sent-events antivirus asgi
1个回答
0
投票

解释

我公司已经开启了Sophos Endpoint Security的网页防护功能。根据 此条目在 Sophos 的社区缓冲和扫描 textevent-stream 内容,以防止恶意软件。因此出现了意外的缓冲。

解决方案

我找到了两个变通的办法。

  1. 在第一个事件之前发送一个2兆字节的数据(多一点也行,少一点就不行)。你不需要在每个事件中都发送这个数据,只需要在第一个事件中发送。
  2. 或者使用https(SSLTLS)。对于本地开发,可以考虑使用 mkcert 以获得一个方便的设置方法。

蟒蛇之谜

上述问题不仅仅是我的代码的问题,也不是Uvicorn、Hypercorn或ASGI的问题。事实上,我甚至尝试了aiohttp的实现,结果也是一样的。然而,当我尝试了 a Go实例实施 上交所,以及 另一个在Node.js中 工作得很好,不需要任何变通方法。我看到的唯一区别是Go的实现在每个事件后都使用了一个flush方法。我不确定为什么ASGI和aiohttp不公开某种刷新方法,或者,如果他们公开了,为什么我找不到它。如果它们有,是否会使这些变通方法变得没有必要?我不确定。

下面是更新后的代码,让它与Sophos一起工作,并检查是否通过https服务。

async def app(scope, receive, send):
    headers = [(b"content-type", b"text/html")]
    if scope["path"] == "/":
        body = (
            "<!DOCTYPE html>"
            "<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})
        if scope["scheme"] != "https": # Sophos will buffer, so send 2MB first
            two_meg_chunk = "." * 2048 ** 2
            await send(
                {
                    "type": "http.response.body",
                    "body": f": {two_meg_chunk}\n\n".encode(),
                    "more_body": True,
                }
            )
        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""})
© www.soinside.com 2019 - 2024. All rights reserved.