如何在tornado服务器上进行长轮询,同时向服务器发送数据?

2024-10-04 05:31:07 发布

您现在位置:Python中文网/ 问答频道 /正文

目标

我想实现一个仪表板(网页),它允许授权连接的客户机与服务器(运行Tornado,Python的服务器)交互。当前授权的客户机存储在服务器上的“客户机阵列”中。仪表板正在长时间轮询更新,因此一旦发生更改,它就会显示阵列中的更改。
由于客户机可以通过仪表板中的按钮进行授权,因此必须同时向服务器发送授权请求以进行长轮询。你知道吗

顺便说一句:我知道,对于这个特定的用例,长轮询是没有必要的。但是项目后面还有其他需要长时间轮询的情况,所以我也尝试将其用于这个情况。你知道吗

当前行为和问题

加载仪表板(以及当前连接的客户端)后,仪表板将开始长时间轮询更新。服务器知道此连接并等待更改发生。
当通过按键对客户机进行授权时,服务器接收到授权请求并分别更改客户机阵列。这些更改导致服务器将更新的HTML发送到仪表板。之后,它将发送另一个对授权请求的响应。你知道吗

但是仪表板没有接收到服务器发送的更新的HTML。好像它从未被发送过,或者仪表板不知何故不再等待它。
不过,通过按钮触发的授权请求的响应将被接收。你知道吗

有人能解释我做错了什么吗?你知道吗

注意下面的编辑

代码

仪表板的代码(JavaScript):

window.onload = function() {
    requestClientUpdates();
};

async function requestClientUpdates() {
    let response = await fetch(window.location.pathname, {
        method: 'POST',
        headers: {
            "Content-Type": "application/x-www-form-urlencoded"
        },
        body: "req-type=update"  // request for getting updates
    });

    if (response.status === 502) {
        console.log("502: " + response.statusText);
    } else if (response.status !== 200) {
        console.log(response.statusText);
    } else {
        updateDashboardHTML(response.text());   // dummy function
        // this doesn't ever get called
    }

    requestClientUpdates();  // immediately poll for updates again
}

// triggered on button-press
function authorizeClient() {
    var data = "req-type=authorize-client";   // along with some more data
    let response = fetch(window.location.pathname, {
        method: 'POST',
        headers: {
            "Content-Type": "application/x-www-form-urlencoded"
        },
        body: data
    });

    if (response.status === 502) {
        console.log("502: " + response.statusText);
    } else if (response.status !== 200) {
        console.log(response.statusText);
    } else {
        console.log("authorized");
        // gets called
    }
}

用于长时间轮询和命令请求的服务器处理程序(带有Tornado框架的Python):

class ClientManager(object):
    def __init__(self):
        self.condition = tornado.locks.Condition()
        self.authorized_clients = []

    def authorize_client(self, id):
        """Adds client with given ID to connected clients"""
        client = get_client(id)  # returns client object with more data
        self.authorized_clients.append(client)
        self.announce_update()

    def announce_update(self):
        self.condition.notify()


client_manager = ClientManager()


class MainHandler(BaseHandler):
    def send_current_client_overview(self):
        data = generatingHTMLforDashboard()  # dummy for the actual code
        self.write(data)  # is reached properly (as tests have shown)

    async def post(self):
        req_type = self.get_arguments("req-type")
        if "authorize-client" in req_type:
            client_manager.authorize_client(self.get_argument("id"))
        elif "update" in req_type:
            await client_manager.condition.wait()
            self.send_current_client_overview()

def main():
    app.listen(port)  # server application was prepared before
    tornado.ioloop.IOLoop.current().start()

编辑

[见下文评论]收到回复,但仅在特定情况下。在服务器重新启动并且仪表板重新进入之后(不仅仅是刷新)。以前不能运行JavaScript)。一旦仪表板被刷新,它就不再工作,服务器必须重新启动等等

编辑2

进行了多项更改:

等待回复.text(),即使您已经在等待响应。否则,承诺“回应”可能还没有实现。你知道吗

async function requestClientUpdates() {
    let response = await fetch(window.location.pathname, {
        // pack request
    });

    // catch errors
    else {
        var answer = await response.text();  // make sure it is fulfilled before usage
        // continue working with answer
    }

    requestClientUpdates();  // immediately poll for updates again
}

#2(这导致了问题):一旦连接关闭,就取消未来。因此,覆盖处理程序的on\u connection\u close():

class MainHandler(BaseHandler):
    def initialize(self):
        self.wait_future = None

    async def post(self):
        if "update" in req_type:
            # save Future returned here so it can be canceled on on_connection_close()
            self.wait_future = client_manager.condition.wait()
            try:
                await self.wait_future
            # catch exception in case future was cancelled due to a closed connection
            except asyncio.CancelledError:
                return
            self.send_current_client_overview()

    def on_connection_close(self):
        if self.wait_future is not None:
            self.wait_future.cancel()

在刷新页面后,这些更改仍然会使代码在fortox的控制台中打印一个TypeError: NetworkError when attempting to fetch resource.,但除此之外,仪表板的行为与预期一致。你知道吗


Tags: self服务器clientdata客户机ifresponsedef