我正在为一个活动代码生成器编写一个python语言插件,该生成器调用我们的Rest API。在多次尝试使用请求库和failing之后,我选择使用更低级别的套接字和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)
对此事的任何见解将不胜感激!
编辑:我忘了提到我在服务器端调试了响应,一切都很正常。
答案 0 :(得分:1)
您不能假设您只能调用recv()
一次并获取所有数据,因为TCP连接只会缓冲有限的数量。此外,您没有解析任何标题以确定您期望的正文大小。你可以使用一个非阻塞套接字并继续阅读直到它阻塞,这将主要起作用但是根本不可靠而且做法很差,所以我不打算在这里记录它。
HTTP正是出于这个原因指明正文大小的方法,如果您希望代码可靠,正确的方法是使用它们。有两件事需要注意。首先,如果HTTP响应有一个Content-Length
,则表示响应正文中将出现多少字节 - 你需要继续阅读,直到你有这么多。第二个选项是服务器可能会向您发送使用chunked encoding的响应 - 它通过包含Transfer-Encoding
标头来指示此情况,其标头的值将包含文本chunked
。我不会在这里进行分块编码,请阅读wikipedia article了解详细信息。本质上,正文包含每个“数据块”的小标题,表示该块的大小。在这种情况下,您必须继续读取块,直到您得到一个空块,这表示响应结束。当服务器开始发送响应主体的大小时,使用此方法代替Content-Length
。
通常,服务器不会同时使用Content-Length
和chunked编码,但实际上没有任何东西可以阻止它,所以这也是需要考虑的事情。如果您只需要与特定服务器进行互操作,那么您可以告诉它它的作用并使用它,但请注意,您将使您的代码不那么便携,并且对未来的更改更加脆弱。
请注意,在使用这些标头时,您仍然需要读取循环,因为任何给定的读取操作都可能返回不完整的数据 - TCP旨在停止发送数据,直到读取应用程序开始清空缓冲区,因此不是你可以解决的问题。另请注意,每次读取可能甚至不包含完整的块,因此您需要跟踪当前块的大小以及您已经看到的大小的状态。您只知道在看到前一个块头指定的字节数时读取下一个块头。
当然,如果您使用Python的无数HTTP库,您不必担心任何此类问题。作为以前必须实现相当完整的HTTP / 1.1客户端的人,你真的想让别人去做,如果你可能的话 - 还有一些棘手的角落要考虑,你上面的简单代码将会失败很多情况。如果requests
不适合您,您是否尝试过任何标准Python库?更高级别的界面有urllib
和urllib2
,而httplib
提供了一种较低级别的方法,您可以找到它来解决您的一些问题。
请记住,如果您真的必须修复问题,或者可能只是导入它们并修补您的更改,您可以随时修改这些代码(在复制到本地存储库之后)。您必须是很明显,这是图书馆的一个问题,而不仅仅是错误地使用它。
如果你真的想要实现一个很好的HTTP客户端,但要注意它比它看起来更难。
最后一点,我总是使用SSL套接字的read()
方法而不是recv()
- 我希望它们是等价的,但是你可能希望尝试一下仍然有问题。