0


我正在为调用我们的 Rest API 的活动代码生成器编写一个 python 语言插件。在多次尝试使用 requests 库但失败后,我选择使用更低级别的 socket 和 ssl 模块,这些模块到目前为止运行良好。我正在使用一种非常粗略的方法来解析响应;对于正文中相当短的响应,这很好用,但我现在正在尝试检索更大的 json 对象(用户列表)。响应被截断如下(注意:为简洁起见,我删除了几个用户条目):
{"page-start":1,"total":5,"userlist":[{"userid":"jim.morrison","first-name":"Jim","last-name":"Morrison","language":"English","timezone":"(GMT+5:30)CHENNAI,KOLKATA,MUMBAI,NEW DELHI","currency":"US DOLLAR","roles":
在此之后应该有更多用户,并且响应正文在控制台中的一行上。

这是我用来从 Rest API 服务器请求用户列表的代码:

import socket, ssl, json

host = self.WrmlClientSession.api_host
port = 8443
pem_file = "<pem file>"

url = self.WrmlClientSession.buildURI(host, port, '<root path>')

#Create the header
http_header = 'GET {0} HTTP/1.1\n\n'
req = http_header.format(url)

#Socket configuration and connection execution
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
conn = ssl.wrap_socket(sock, ca_certs = pem_file)
conn.connect((host, port))
conn.send(req)

response = conn.recv()
(headers, body) = response.split("\r\n\r\n")

#Here I would convert the body into a json object, but because the response is 
#cut off, it cannot be properly decoded.  
print(response) 

任何对此问题的见解将不胜感激!

编辑:我忘了提到我在服务器端调试了响应,一切都很正常。

4

1 回答 1

3

您不能假设您可以只调用recv()一次并获取所有数据,因为 TCP 连接只会缓冲有限的数量。此外,您不会解析任何标题来确定您期望的正文大小。您可以使用非阻塞套接字并继续阅读,直到它阻塞,这将主要工作,但根本不可靠且实践非常差,所以我不打算在这里记录它。

正是出于这个原因,HTTP 有方法指示正文的大小,如果您希望代码可靠,正确的方法是使用它们。有两件事要寻找。首先,如果 HTTP 响应有一个Content-Lengththen 表示响应正文中将出现多少字节 - 你需要继续阅读,直到你有那么多。第二个选项是服务器可能会向您发送一个使用分块编码的响应- 它通过包含一个Transfer-Encoding其值将包含文本的标头来表明这一点chunked。我不会在这里进行分块编码,请阅读维基百科文章详情。本质上,主体包含每个“块”数据的小标题,指示该块的大小。在这种情况下,您必须继续读取块,直到获得一个空块,这表示响应结束。Content-Length当服务器开始发送响应主体时,它不知道响应主体的大小,而是使用这种方法。

通常,服务器不会同时使用Content-Length分块编码,但实际上没有什么可以阻止它,所以这也是需要考虑的事情。如果您只需要与特定服务器进行互操作,那么您可以只知道它做了什么并使用它,但请注意,您将使您的代码的可移植性降低,并且对未来的更改更加脆弱。

请注意,使用这些标头时,您仍然需要循环读取,因为任何给定的读取操作都可能返回不完整的数据 - TCP 旨在停止发送数据,直到读取应用程序开始清空缓冲区,所以这不是你可以解决的问题。另请注意,每次读取甚至可能不包含完整的块,因此您需要跟踪有关当前块大小的状态以及您已经看到的块的数量。当您看到前一个块头指定的字节数时,您才知道读取下一个块头。

当然,如果您使用 Python 的无数 HTTP 库中的任何一个,您不必担心这些。作为一个之前必须实现一个相当完整的 HTTP/1.1 客户端的人,如果可能的话,您真的希望让其他人来做 - 有很多棘手的极端情况需要考虑,并且您上面的简单代码将会失败很多案例。如果requests对您不起作用,您是否尝试过任何标准 Python 库?有urlliburllib2用于更高级别的接口,并httplib提供了一种较低级别的方法,您可能会发现它可以让您解决一些问题。

请记住,如果您确实必须修复问题,或者可能只是导入它们并猴子修补您的更改,您可以随时修改这些代码(当然是在复制到本地存储库之后)。您必须非常清楚不过,这是图书馆中的一个问题,而不仅仅是错误使用它。

如果您真的想实现一个 HTTP 客户端,那很好,但请注意它比看起来更难。

最后,我一直使用read()SSL 套接字的方法而不是recv()- 我希望它们是等效的,但是如果您仍然遇到问题,您可能希望尝试一下。

于 2013-03-09T10:13:32.220 回答