【问题标题】:Is it possible to override the default socket options in requests?是否可以覆盖请求中的默认套接字选项?
【发布时间】:2013-02-15 09:25:55
【问题描述】:

我使用优秀的 Python 请求库为 rest API 编写了一个非常简单的客户端。一切都很好,我通过负载均衡器运行客户端,它可以正常检测空闲的 tcp 连接并杀死它们。我希望我的客户使用一些不同的 tcp 保持活动选项,而不是我的平台(linux)上的默认值。但是我没有看到任何简单的方法来告诉套接字库我想为新套接字选择一些默认选项。

直接使用 socket.create_connection 时,使用装饰器很容易做到这一点,但我不知道当实际调用被埋在某个第 3 方库中时,我如何使装饰调用可用,例如请求。

提前致谢

【问题讨论】:

  • 让我猜猜:天蓝色?

标签: python sockets tcp


【解决方案1】:

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)

【讨论】:

  • 应该是 requests.Session() 吗?
  • @JasonHeiss 这些是可以互换的。
  • 这个答案有效。请记住,套接字选项会覆盖默认值。如果要与默认套接字选项合并,则应使用HTTPConnection.default_socket_options + [ (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), ]
  • 拯救我的一天。我在使用请求发送 POST 时发现了一个问题,但如果服务器长时间没有响应,比如 5 分钟,则连接被重置。这真的很有帮助。并注意到 socket_options 是平台相关的
【解决方案2】:

requests 使用urllib3,它使用标准库的http.client(或httplib,对于2.x),它调用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,它可能就像使用 httplib 而不是上面的 http.client 一样简单,但您可能需要验证这一点。

【讨论】:

  • 很棒的解决方案,我没有意识到模块的作用域是这样工作的。
  • 现在可以通过HTTPConnection.socket_optionsHTTPConnection.default_socket_options 完成,因为urllib3 1.8.3。
【解决方案3】:

另一个可用的替代方法是 requests_toolbelt 使用 TCPKeepAliveAdapter

这背后是设置请求 HTTPAdapter 的套接字,并考虑到您的 OSX 特性。

https://toolbelt.readthedocs.io/en/latest/adapters.html#tcpkeepaliveadapter

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',
             # ...
           )

【讨论】:

    【解决方案4】:

    FireFox、Chrome、Edge 或 Safari 等所有浏览器都将使用非常频繁的 TCP keepalives 来确保已建立的 TCP 连接保持建立,并在连接断开时重新连接。在已建立的 TCP 连接上,有三个可配置的属性确定 keepalive 的工作方式。在 Linux 上它们是:

    1. tcp_keepalive_time(默认 7200 秒)
    2. tcp_keepalive_probes(默认 9)
    3. tcp_keepalive_intvl(默认 75 秒)

    Python 请求永远不会在套接字上启用 TCP keepalive(在 Linux 上,默认情况下 TCP keepalive 未在套接字上启用,应用程序必须启用它)。 Python 请求使用每个操作系统上的默认套接字选项,因此对于 HTTP 1.1 持久连接,如果连接保持空闲,我们将不知道是否断开已建立的连接。在断开的连接上,我们只会知道下一次套接字写入发生的时间。使用低于默认值的 tcp_keepalive_time 有助于诊断断开的空闲连接。 tcp_keepalive_intvl 是两个 keepalive 之间的间隔。

    在下面的代码中,我们使用请求推荐的方式使用用户定义的 HTTPAdapter 通过底层 urllib3 设置套接字选项。 (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) 是启用keepalives,另外两个是设置tcp_keepalive_time和tcp_keepalive_intvl为10秒。

    记住 TCP keepalive 是平台相关的。此代码仅适用于 Linux。

    import requests, socket
    from requests.adapters import HTTPAdapter
    
    class HTTPAdapterWithSocketOptions(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)
    
    KEEPALIVE_INTERVAL = 10
    adapter = HTTPAdapterWithSocketOptions(socket_options=[(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
    (socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, KEEPALIVE_INTERVAL), (socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, KEEPALIVE_INTERVAL)])
    s = requests.Session()
    s.mount("http://", adapter)
    s.mount("https://", adapter)
    

    【讨论】:

    • 感谢您提供此代码 sn-p,它可能会提供一些有限的即时帮助。一个适当的解释将通过展示为什么这是解决问题的好方法来极大地提高其长期价值,并使其对有其他类似问题的未来读者更有用。请编辑您的答案以添加一些解释,包括您所做的假设
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-12
    相关资源
    最近更新 更多