Python中HTTP POST请求的不一致行为

6 投票
1 回答
1255 浏览
提问于 2025-04-17 14:45

我正在尝试在一个Python(WSGI)和一个NodeJS + Express应用之间发送POST请求。它们在不同的服务器上。

问题是,当使用不同的IP地址时(比如私有网络和公共网络),在公共网络上使用urllib2的请求是成功的,但在私有网络上的同样请求却失败了,出现了502 Bad Gateway或者URLError [32] Broken pipe的错误。

我使用的urllib2代码是这样的:

req = urllib2.Request(url, "{'some':'data'}", {'Content-Type' : 'application/json; charset=utf-8'})

res = urllib2.urlopen(req)

print f.read()

现在,我也用requests库这样写了请求:

r = requests.post(url, headers = {'Content-Type' : 'application/json; charset=utf-8'}, data = "{'some':'data'}")

print r.text

并且得到了200 OK的响应。这个替代的方法在两个网络上都能正常工作。

我想知道是否需要对urllib2的请求做一些额外的配置,我可能不知道,或者是否需要查看一些可能缺失的网络配置(我不认为是这个问题,因为替代请求方法可以正常工作,但我也可能错了)。

如果有任何建议或指点,我将非常感激。谢谢!

1 个回答

3

这里的问题是,正如Austin Phillips指出的,urllib2.Request的构造函数中的data参数:

可以是一个字符串,用来指定发送给服务器的额外数据…… data应该是标准的application/x-www-form-urlencoded格式的缓冲区。urllib.urlencode()函数接受一个映射或一系列二元组,并返回这种格式的字符串。

如果你传递的是JSON编码的数据而不是url编码的数据,就会让它感到困惑。

不过,Request有一个方法add_data

将请求数据设置为data。除了HTTP处理程序外,所有处理程序都会忽略这个——在HTTP处理程序中,它应该是一个字节字符串,并且会将请求更改为POST而不是GET。

如果你使用这个方法,可能还应该使用add_header,而不是在构造函数中传递,尽管文档中似乎没有特别提到这一点。

所以,这样做应该可以:

req = urllib2.Request(url)
req.add_data("{'some':'data'}")
req.add_header('Content-Type', 'application/json; charset=utf-8')
res = urllib2.urlopen(req)

在一条评论中,你说:

我不想直接切换到requests,而是想找出我为什么会遇到这个问题,因为可能有一些更深层次的潜在问题,这可能会导致以后更难发现的问题。

如果你想找出深层次的问题,光看客户端的源代码是没用的。要弄清楚“为什么X能工作而Y失败?”的第一步是确切了解X和Y各自发送了什么字节。然后你可以尝试缩小相关的差异,找出代码的哪个部分导致Y在相关位置发送错误的数据。

你可以通过在服务端记录日志(如果你能控制它)、运行Wireshark等方式来做到这一点,但对于简单的情况,最简单的方法是使用netcat。你需要查阅man nc来了解你系统的用法(在Windows上,你需要先获取并安装netcat才能运行),因为每个版本的语法都不同,但通常都是类似nc -kl 12345这样简单的命令。

然后,在你的客户端中,将URL更改为使用localhost:12345替代主机名,这样就会连接到netcat并发送HTTP请求,所有内容会显示在终端上。你可以复制这些内容,然后使用nc HOST 80并粘贴,看看真实服务器的响应,从而缩小问题的范围。或者,如果你遇到困难,至少可以将数据复制粘贴到你的SO问题中。


最后一件事:这几乎肯定与您的问题无关(因为你用requests发送的完全相同的数据是有效的),但你的数据实际上并不是有效的JSON,因为它使用了单引号而不是双引号。根据文档string被定义为:

string
    ""
    " chars "

(文档中也有很好的图示。)

一般来说,除了非常简单的测试案例外,你不想手动编写JSON。在很多情况下(包括你的情况),你只需将"…"替换为json.dumps(…),所以这并不是一个严重的问题。所以:

req = urllib2.Request(url)
req.add_data(json.dumps({'some':'data'}))
req.add_header('Content-Type', 'application/json; charset=utf-8')
res = urllib2.urlopen(req)

那么,为什么它能工作呢?在JavaScript中,单引号字符串是合法的,还有一些像反斜杠转义这样的东西在JSON中是无效的,任何使用restricted-eval(或者更糟糕的,原始eval)进行解析的JS代码都会接受它。而且,由于很多人习惯于写错误的JSON,许多浏览器的原生JSON解析器和其他语言的许多JSON库都有解决常见错误的变通办法。但你不应该依赖这些。

撰写回答