是否可以重写请求中的默认套接字选项?

10 投票
5 回答
7776 浏览
提问于 2025-04-17 17:31

我用Python的requests库写了一个非常简单的客户端,用来访问一个REST API。一切运行得很好,直到我通过负载均衡器运行这个客户端。负载均衡器会智能地检测到闲置的TCP连接并把它们关闭。我希望我的客户端能使用一些不同于我平台(Linux)默认的TCP保持连接选项。但是,我找不到简单的方法告诉socket库,让我为新的socket选择一些默认选项。

直接使用socket.create_connection时,这个操作比较简单,可以通过一个装饰器来实现,但我不知道当实际调用被埋在某个第三方库里(像requests那样)时,如何让这个装饰器的调用变得可用。

提前谢谢你!

5 个回答

3

另一个可用的选择是 requests_toolbelt,它使用了TCPKeepAliveAdapter。

这个工具背后其实是在设置请求的HTTP适配器的连接方式,并且考虑了你使用的操作系统(比如说Mac OS)的特性。

你可以在这里了解更多信息

import requests
from requests_toolbelt.adapters.socket_options import TCPKeepAliveAdapter

session = requests.Session()
keep_alive = TCPKeepAliveAdapter(idle=120, count=20, interval=30)
session.mount('https://region-a.geo-1.compute.hpcloudsvc.com', keep_alive)
session.post('https://region-a.geo-1.compute.hpcloudsvc.com/v2/1234abcdef/servers',
             # ...
           )
6

requests库使用了urllib3,而urllib3又依赖于标准库中的http.client(在2.x版本中是httplib),最终调用socket.create_connection,这一切都没有提供可以插入自定义代码的地方。

所以,你要么得自己修改其中一个库,要么就得在运行时动态地修改它。

最简单的修改地方可能是在http.client.connect,因为这个函数只是简单地包装了socket.create_connection,很容易就能替换掉:

orig_connect = http.client.HTTPConnection.connect
def monkey_connect(self):
    orig_connect(self)
    self.sock.setsockopt(…)
http.client.HTTPConnection.connect = monkey_connect

如果你使用的是2.x版本,可能只需要把上面的http.client换成httplib就行,但你可能还是要确认一下。

14

更新版的 urllib3(从1.8.3版本开始,发布于2014年6月23日)支持设置套接字选项。

你可以通过创建一个自定义适配器来从 requests(从2.4.0版本开始,发布于2014年8月29日)设置这些选项:

class HTTPAdapterWithSocketOptions(requests.adapters.HTTPAdapter):
    def __init__(self, *args, **kwargs):
        self.socket_options = kwargs.pop("socket_options", None)
        super(HTTPAdapterWithSocketOptions, self).__init__(*args, **kwargs)

    def init_poolmanager(self, *args, **kwargs):
        if self.socket_options is not None:
            kwargs["socket_options"] = self.socket_options
        super(HTTPAdapterWithSocketOptions, self).init_poolmanager(*args, **kwargs)

然后,你可以把这个适配器应用到需要自定义套接字选项的会话上(比如设置 SO_KEEPALIVE):

adapter = HTTPAdapterWithSocketOptions(socket_options=[(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)])
s = requests.session()
s.mount("http://", adapter)
s.mount("https://", adapter)

撰写回答