【问题标题】:Send intermittent status of request before sending actual response在发送实际响应之前发送请求的间歇状态
【发布时间】:2018-01-01 08:16:37
【问题描述】:

我有一个服务器,它需要几分钟来处理一个特定的请求,然后响应它。

客户端必须一直等待响应而不知道何时完成。

有没有办法让客户知道处理状态? (比如说完成了 50%,完成了 80%),而无需客户端轮询状态。

【问题讨论】:

  • 我可以使用服务器推送来实现吗?
  • +1 uwsgi。除此之外还有什么?通常人们使用bottle、flask、django等来处理请求。
  • 使用您的服务器处理函数同时启动一个带有停止事件的线程,让线程在服务器完成工作设置线程停止事件(因此线程停止)并将真正的响应发送给客户端
  • 太棒了。我添加了一个小型 uwsgi 服务器示例。输出与第一台服务器相同。

标签: python http client-server uwsgi wsgi


【解决方案1】:

此答案可能对您的特定情况没有帮助,但在其他情况下可能会有所帮助。

HTTP协议支持informational (1xx) responses

表示临时 用于传达连接状态或请求进度的响应 在完成请求的操作并发送最终结果之前 回应

甚至还有一个状态码正好适合您的用例,102 (Processing)

临时响应用于 通知客户端服务器已接受完整的请求, 但还没有完成

由于缺乏实现,状态码 102 已从该标准的其他版本中删除,但它仍然是 registered 并且可以使用。

所以,它可能看起来像这样(HTTP/2 有一个等效的二进制形式):

HTTP/1.1 102 Processing
Progress: 50%

HTTP/1.1 102 Processing
Progress: 80%

HTTP/1.1 200 OK
Date: Sat, 05 Aug 2017 11:53:14 GMT
Content-Type: text/plain

All done!

很遗憾,这并未得到广泛支持。特别是,WSGI 不提供发送任意 1xx 响应的方法。客户端支持 1xx 响应,因为它们需要解析和容忍它们,但它们通常不提供对它们的编程访问权限:在此示例中,Progress 标头将不可用于客户端应用程序。

但是,1xx 响应可能仍然有用(如果服务器可以发送它们),因为它们具有重置客户端的套接字读取超时的效果,这是响应缓慢的主要问题之一。

【讨论】:

  • +1。这看起来是从服务器发送处理更新的理想方式,但不幸的是我看不到在 WSGI 中实现它的方式
【解决方案2】:

使用分块传输编码,这是一种传输未知长度流的标准技术。

见:Wikipedia - Chunked Transfer Encoding

这里有一个 Python 服务器实现,可作为 GitHub 上的要点:

它使用标准库模块使用分块传输编码发送内容

在客户端,如果服务器已经通知了分块传输编码,你只需要:

import requests

response = requests.get('http://server.fqdn:port/', stream=True)
for r in response.iter_content(chunk_size=None):
    print(r)

chunk_size=None,因为块是动态的,将由块传输语义的简单约定中的信息确定。

见:http://docs.python-requests.org/en/master/user/advanced/#chunk-encoded-requests

当您在响应r 的内容中看到例如100 时,您知道下一个块将是处理100 后的实际内容。

【讨论】:

    【解决方案3】:

    在没有使用任何较新的技术(websockets、webpush/http2、...)的情况下,我之前使用了简化的 PushletLong polling 解决方案用于 HTTP 1.1和各种javascript或者自己的客户端实现。如果我的解决方案不适合您的用例,您可以随时搜索这两个名称以了解更多可能的方法。

    客户 发送请求,读取 17 个字节(初始 http 响应),然后一次读取 2 个字节获取处理状态。

    服务器 发送一个有效的 HTTP 响应,并在请求过程中发送 2 个字节的完成百分比,直到最后 2 个字节“正常”并关闭连接。

    更新:示例 uwsgi server.py

     from time import sleep
     def application(env, start_response):
         start_response('200 OK', [])
    
         def working():
             yield b'00'
             sleep(1)
             yield b'36'
             sleep(1)
             yield b'ok'
         return working()
    

    更新:示例请求 client.py

    import requests
    
    response = requests.get('http://localhost:8080/', stream=True)
    for r in response.iter_content(chunk_size=2):
        print(r)
    

    示例服务器(仅用于测试:)

    import socket
    from time import sleep
    HOST, PORT = '', 8888
    
    listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    listen_socket.bind((HOST, PORT))
    listen_socket.listen(1)
    
    while True:
        client_connection, client_address = listen_socket.accept()
        request = client_connection.recv(1024)
        client_connection.send('HTTP/1.1 200 OK\n\n')
        client_connection.send('00')  # 0%
        sleep(2)  # Your work is done here
        client_connection.send('36')  # 36%
        sleep(2)  # Your work is done here
        client_connection.sendall('ok')  # done
        client_connection.close()
    

    如果最后 2 个字节不是“ok”,则以其他方式处理错误。这不是漂亮的 HTTP 状态代码合规性,而是多年前对我有用的解决方法。

    telnet 客户端示例

    $ telnet localhost 8888
    Trying 127.0.0.1...
    Connected to localhost.
    Escape character is '^]'.
    GET / HTTP/1.1
    HTTP/1.1 200 OK
    
    0036okConnection closed by foreign host.
    

    【讨论】:

    • 这是一个很棒的答案。我从中学到了很多(并且仍在学习..)。谢谢!
    • 乐于助人。请记住,由于项目限制,此解决方案更多的是 av 解决方法,需要通过所有路径设置适当的超时等。我认为我使用此解决方案的第一个客户是在 J2ME 中。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-06
    • 2014-05-02
    • 1970-01-01
    • 1970-01-01
    • 2020-07-18
    • 2015-07-15
    相关资源
    最近更新 更多