如何在 Python 线程中中止/取消 HTTP 请求?

Gra*_*ntJ 11 python sockets multithreading http

我正在寻找中止/取消 Python 线程中的 HTTP 请求。我必须坚持使用线程。我不能使用 asyncio 或标准库之外的任何东西。

此代码适用于套接字:

"""Demo for Canceling IO by Closing the Socket

Works!

"""

import socket
import time

from concurrent import futures

start_time = time.time()

sock = socket.socket()


def read():
    "Read data with 10 second delay."
    sock.connect(('httpbin.org', 80))
    sock.sendall(b'GET /delay/10 HTTP/1.0\r\n\r\n')
    while True:
        data = sock.recv(1024)
        if not data:
            break
        print(data.decode(), end='')


with futures.ThreadPoolExecutor() as pool:
    future = pool.submit(read)
    futures.wait([future], timeout=5)
    sock.close()  # <-- Interrupt sock.recv(1024) in Thread:read().

end_time = time.time()
print(f'Duration: {end_time - start_time:.3f}')

# Duration is ~5s as expected.
Run Code Online (Sandbox Code Playgroud)

关闭主线程中的socket用于中断执行器池线程中的recv()。HTTP 请求应该需要 10 秒,但我们只等待 5 秒然后关闭套接字(有效地取消 HTTP 请求/响应)。

现在我尝试使用 http.client:

"""Demo for Canceling IO in Threads with HTTP Client

Doesn't work!

"""

import time

from concurrent import futures

from http.client import HTTPConnection


def get(con, url):
    con.request('GET', url)
    response = con.getresponse()
    return response


start_time = time.time()

with futures.ThreadPoolExecutor() as executor:
    con = HTTPConnection('httpbin.org')
    future = executor.submit(get, con, '/delay/10')
    done, not_done = futures.wait([future], timeout=5)
    con.sock.close()

end_time = time.time()
print(f'Duration: {end_time - start_time:.3f}')

# Duration is ~10s unfortunately.
Run Code Online (Sandbox Code Playgroud)

不幸的是,这里的总持续时间约为 10 秒。关闭套接字不会中断客户端中的 recv_into()。

好像我做出了一些错误的假设。如何从单独的线程中断 http 客户端中使用的套接字?

pyg*_*eek 5

您所描述的是预期的有据可查的行为:

注意 close() 释放与连接关联的资源,但不一定立即关闭连接。如果要及时关闭连接,请在 close() 之前调用 shutdown()。

有关此行为的一些进一步详细信息仍然可以在 CPython howto 文档中找到:

严格来说,您应该在关闭套接字之前对其使用 shutdown 。关闭是对另一端套接字的建议。根据您传递的参数,它可能意味着“我不会再发送,但我仍然会听”,或者“我不听,很好的摆脱!”。然而,大多数套接字库已经习惯了程序员忽略使用这一礼仪,通常关闭与 shutdown() 相同;关闭()。因此在大多数情况下,不需要显式关闭。

有效使用 shutdown 的一种方法是使用类似 HTTP 的交换。客户端发送请求,然后执行关闭(1)。这告诉服务器“该客户端已完成发送,但仍然可以接收。” 服务器可以通过接收 0 字节来检测“EOF”。它可以假设它具有完整的请求。服务器发送回复。如果发送成功完成,那么客户端确实仍在接收。

Python 在自动关闭方面更进了一步,它表示当套接字被垃圾回收时,如果需要它会自动关闭。但依赖这一点是一个非常不好的习惯。如果您的套接字在没有执行关闭的情况下就消失了,另一端的套接字可能会无限期地挂起,认为您只是速度慢。完成后请关闭套接字。

解决方案

关闭之前调用 shutdown。

例子

with futures.ThreadPoolExecutor() as executor:
    con = HTTPConnection('httpbin.org')
    future = executor.submit(get, con, '/delay/10')
    done, not_done = futures.wait([future], timeout=5)
    con.sock.shutdown()
    con.sock.close()
Run Code Online (Sandbox Code Playgroud)

参考

Python Socket 对象 - 关闭:https ://docs.python.org/3/library/socket.html#socket.socket.close

CPython Howto 套接字 - 断开连接:https://github.com/python/cpython/blob/65460565df99fbda6a74b6bb4bf99affaaf8bd95/Doc/howto/sockets.rst#disconnecting

  • 我有点失望。不是答案 - 没关系 - 而是引用文档中关于_“忽略使用这条礼仪”_在_“向另一端的套接字提供咨询”_的上下文中的部分。这与礼仪无关,而是与 TCP FIN 数据包和 TCP 状态有关。 (3认同)
  • `socket.shutdown()` 带有一个参数。在大多数情况下,我们想要半关闭(发送 EOF),但在这里我们想要完全关闭。 (2认同)