【问题标题】:Python Requests splits TCP packetPython 请求拆分 TCP 数据包
【发布时间】:2018-10-17 04:54:50
【问题描述】:

我正在尝试使用 python 编写 HTTP POST 请求。

在 bash 中使用 curl 进行尝试时,一切正常。使用 python,使用 requestsurllib3-library,我从 API 收到错误响应。 POST 请求包含标头中的信息和请求正文中的 json。

我注意到,当我使用 Wireshark 拦截数据包时,curl-request(正在工作)是一个长度为 374 字节的单个数据包。 python-request(requestsurllib3 在这里没有区别)被分成 253 和 144 字节长度的 2 个单独的数据包。

Wireshark 可以毫无问题地重新组装这些内容,并且它们似乎都包含标题和 POST 正文中的完整信息。但我尝试连接的 API 以一个不太有用的“处理请求时出错”来回答。

由于 253 字节不能是 TCP 数据包的限制,这种行为的原因是什么?有办法解决吗?

编辑:

重击:

curl 'http://localhost/test.php' -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36' -H 'Content-Type: application/json' -d '{"key1":"value1","key2":"value2","key3":"value3"}'

蟒蛇:

import requests, json

headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
"Content-Type": "application/json"}

data = {"key1":"value1", "key2":"value2", "key3":"value3"}

r=requests.post("http://localhost/test.php", headers=headers, data=json.dumps(data))

【问题讨论】:

  • 您能否添加您正在使用的生成这些结果的示例代码?
  • 对不起,我在下面添加了没有敏感信息的代码!

标签: python tcp python-requests


【解决方案1】:

TCP 是一个数据流,而不是一系列消息。将数据流分割成数据包应该与数据流的解释无关,无论是发送者还是接收者。如果接收者实际上基于数据包的分段行为不同,则接收者被破坏。

虽然我见过这样的损坏系统,但我也见过更多的系统因为不同的原因不喜欢该请求,例如错误的用户代理、缺少接受标头或类似情况。我建议你先检查一下,然后再断定它一定是数据流的分段。

至于为什么 curl 和 requests 行为不同:可能 curl 首先构造完整的请求(标​​头和正文)并发送它,而 requests 首先构造标头并发送它然后发送正文,即执行两个可能导致的写入操作分两包。

【讨论】:

  • 由于我对服务器端 API 没有影响,您有什么建议可以在 python 中实现与在 bash 中相同的结果吗?
  • @ChristianH:鉴于我什至不确定问题是什么(请参阅我的回答),因此很难提出任何建议。我通常做的是自己手动创建完整的请求进行测试,使用套接字直接将其发送到服务器,然后玩弄发送和请求,直到我发现真正的问题是服务器不喜欢什么,即特定的标头(或缺少的标头)或实际上是数据流的分段。
【解决方案2】:

虽然这对您遇到的问题无关紧要,但有一种方法可以强制将数据发送到一个数据包中进行多次发送,即使用套接字上的 TCP_CORK 选项(但取决于平台)。

先创建一个适配器:

from requests.packages.urllib3.connection import HTTPConnection

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)

然后将其用于您要发送的请求:

s = requests.Session()
options = HTTPConnection.default_socket_options + [ (socket.IPPROTO_TCP, socket.TCP_CORK, 1)]
adapter = HTTPAdapterWithSocketOptions(socket_options=options)
s.mount("http://", adapter)

【讨论】:

    【解决方案3】:

    遗憾的是,正如@Steffen Ullrich 所解释的那样(尽管它们声称是行业标准),确实存在非常破碎的系统,它们无法处理碎片化的 TCP 帧。由于我的应用程序/脚本相当独立且独立,因此我使用了基于@Roeften 的答案的更简单的workaround,它将 TCP_CORK 应用于所有连接。

    警告:此解决方法仅在您不会冒险破坏依赖requests 的任何其他功能的情况下才有意义。

    requests.packages.urllib3.connection.HTTPConnection.default_socket_options = [(6,3,1)]
    

    【讨论】:

      猜你喜欢
      • 2023-03-15
      • 1970-01-01
      • 2011-07-14
      • 1970-01-01
      • 1970-01-01
      • 2020-01-14
      • 1970-01-01
      • 2013-06-12
      • 2018-09-04
      相关资源
      最近更新 更多