Python 如果参数';车身';请求()的长度不能为len()吗?
代码如下:Python 如果参数';车身';请求()的长度不能为len()吗?,python,http,post,python-2.7,httplib,Python,Http,Post,Python 2.7,Httplib,代码如下: import httplib import cStringIO s = cStringIO.StringIO("hello world") c = httplib.HTTPConnection("xxx.xxx.xxx.xxx") c.request("POST", "/xpost", s) headers = {} if not hasattr(body, "fileno"): headers["Content-Length"] = len(body.getvalue
import httplib
import cStringIO
s = cStringIO.StringIO("hello world")
c = httplib.HTTPConnection("xxx.xxx.xxx.xxx")
c.request("POST", "/xpost", s)
headers = {}
if not hasattr(body, "fileno"):
headers["Content-Length"] = len(body.getvalue())
s
是一个可读取的()对象,因此request()应该发送s
的内容,但是,由于s
不可读取,因此request()发送的数据不包含头内容长度
,也不包含正文
区域中的任何正文内容
那么,当我无法获得要发送的
正文的长度时,如何发送帖子呢?httplib的行为(至少在Python 2.7中)是这样的,它会在添加自己的内容长度头之前检查是否存在内容长度头,因此,如果您碰巧知道内容的大小,则可以添加自己的标题-例如:
c.request("POST", "/xpost", s, headers={"Content-Length": len(s.getvalue())})
如果没有这样的头,httplib
尝试调用len()
自动填充一个,如果失败,它会假定主体必须是一个类似文件的对象,并在操作系统级文件描述符上调用os.fstat()
,以确定其大小-它通过调用fileno()来获得此描述符
方法,该方法位于您给定的文件句柄上。这适用于真实文件,但由于StringIO
对象不是真实文件,因此它们不提供fileno()
方法,操作失败,出现AttributeError
。此错误由httplib
捕获并静默处理,它无法添加内容长度
如果您确实在使用StringIO
对象,最简单的选择可能是添加您自己的Content-Length
标题,如我在上面的示例中所示。如果这只是一个测试,而您正在运行,并且实际使用的是真实的文件,那么只要os.fstat()
在您的平台上工作,您就可以依靠httplib
来正确设置头文件。如果没有,您总是可以自己在文件名上调用os.stat()
,并以相同的方式提供自己的头
如果您想同时处理真实文件和StringIO
,则始终可以执行以下操作:
import httplib
import cStringIO
s = cStringIO.StringIO("hello world")
c = httplib.HTTPConnection("xxx.xxx.xxx.xxx")
c.request("POST", "/xpost", s)
headers = {}
if not hasattr(body, "fileno"):
headers["Content-Length"] = len(body.getvalue())
。。。但我不建议你增加这种复杂性,除非你需要它
最后,在HTTP级别,还有另一个选项可以使用,您不需要提供内容长度
头,主体本身被编码在自描述的数据块中。然而,不幸的是,许多HTTP软件(包括客户端和服务器)(httplib
)倾向于假定只有响应将被分块,请求将始终使用内容长度。我想这个假设是因为请求通常很小,但是当然对于POST
和PUT
来说,这个假设是站不住脚的
假设您确信您的服务器将处理分块请求,您可以尝试这样做-要做到这一点,您需要构建一个StringIO
对象(或任何其他没有fileno()
方法的对象,以击败httplib
的自动内容长度插入),而分块编码已经就位,并为您自己的传输编码
标题提供值分块
。就个人而言,如果您的软件旨在与各种各样的服务器一起工作,我不建议您这样做
编辑:作为旁白,如果使用分块编码,则不得发送内容长度
标题-参见第3项。当然,对于请求,您不能通过简单地关闭连接来发出身体末端的信号,因为这样您就没有接收响应的连接
举例来说,对分块请求的支持是多么糟糕,去年年底才将其添加到核心功能中(尽管之前有)
编辑2:
如果你通读维基百科的文章,你会发现它不仅仅是发送正确的标题——你必须将正文分割成块,然后发送每个块,每个块都有一个小标题,标题由十六进制的块大小组成。这通常在发送响应时为您完成,但正如我提到的,在请求中对它的支持很差
下面是一个围绕类似文件的对象的包装器示例,它将主体转换为块。我已经修改了上面的示例来演示如何使用它,当然,“hello world”
主体非常小,最后只剩下一个块。然而,它应该适用于任何大小的身体。它应该使用read()
方法处理任何对象,该方法的工作方式与Python文件
对象的工作方式相同。事实上,如果将标准Python文件对象封装在其中一个对象中,它将阻止httplib
添加内容长度
,因为它不支持len()
或fileno()
您仍然需要记得自己添加传输编码
头,如下面的示例所示:
import httplib
import cStringIO
class ChunkedEncodingWrapper(object):
def __init__(self, fileobj, blocksize=8192):
self.fileobj = fileobj
self.blocksize = blocksize
self.current_chunk = ""
self.closed = False
def read(self, size=None):
ret = ""
while size is None or size >= len(self.current_chunk):
ret += self.current_chunk
if size is not None:
size -= len(self.current_chunk)
if self.closed:
self.current_chunk = ""
break
self._get_chunk()
else:
ret += self.current_chunk[:size]
self.current_chunk = self.current_chunk[size:]
return ret
def _get_chunk(self):
if not self.closed:
chunk = self.fileobj.read(self.blocksize)
if chunk:
self.current_chunk = "%x" % (len(chunk),) + "\r\n" + chunk + "\r\n"
else:
self.current_chunk = "0\r\n\r\n"
self.closed = True
s = cStringIO.StringIO("hello world")
w = ChunkedEncodingWrapper(s)
c = httplib.HTTPConnection("xxx.xxx.xxx.xxx")
c.request("POST", "/xpost", w, headers={"Transfer-Encoding": "chunked"})
非常感谢您这么长的响应:),但我仍然不知道如何通过httplib
发送分块数据,因为正文是一个unlen()可调用的流,因此正文的长度不可用。我尝试将Transfer Encoding:chunked
添加到标题中,但仍然无法发送分块正文:(您需要做的不仅仅是添加Transfer Encoding
标题-请参阅我在上面的第二次编辑,我在其中编写了一个包装器,您可以将它放在流周围,它将以适合httplib
使用的形式发出块编码数据。