【问题标题】:httplib will not send any body if the param 'body' of request() is not len()able?如果 request() 的参数“body”不是 len()able,httplib 不会发送任何正文?
【发布时间】:2013-07-02 09:14:50
【问题描述】:

这样的代码:

import httplib
import cStringIO

s = cStringIO.StringIO("hello world") 
c = httplib.HTTPConnection("xxx.xxx.xxx.xxx")
c.request("POST", "/xpost", s)

s 是一个 read()able 对象,所以 request() 应该发送 s 的内容,但是,由于 s 不是 len()able,所以 request() 发送的数据可以不包含标题Content-Lengthbody 区域中的任何正文内容。

那么,当我无法获得要发送的body 的长度时,如何发送 POST 呢?

【问题讨论】:

    标签: python http post python-2.7 httplib


    【解决方案1】:

    httplib 的行为(至少在 Python 2.7 中)是这样的,它会在添加自己的标头之前检查是否存在现有的 Content-Length 标头,因此,如果您碰巧知道内容的大小,那么您可以添加您自己的标题 - 例如:

    c.request("POST", "/xpost", s, headers={"Content-Length": len(s.getvalue())})
    

    如果没有这样的标头,httplib 会尝试调用 len() 自动填写一个,如果失败,它会假定正文必须是类文件对象并在操作系统级别调用 os.fstat()文件描述符以确定其大小 - 它通过在您给它的文件句柄上调用 fileno() 方法来获取此描述符。这适用于真实文件,但由于StringIO 对象不是真实文件,它们不提供fileno() 方法并且操作失败并返回AttributeError。这个错误被httplib捕获并静默处理,它根本无法添加Content-Length

    如果您确定使用StringIO 对象,您最简单的选择可能是添加您自己的Content-Length 标头,如我在上面的示例中所示。如果这只是一个测试,而您将在现实中使用真实文件,那么只要os.fstat() 在您的平台上工作,您就可以依靠httplib 正确设置标题。如果没有,您可以随时在文件名上自己调用os.stat(),并以同样的方式提供您自己的标题。

    如果你想同时处理真实文件和StringIO,那么你总是可以这样做:

    headers = {}
    if not hasattr(body, "fileno"):
        headers["Content-Length"] = len(body.getvalue())
    

    ...但我不建议您添加这种复杂性,除非您需要它。

    最后,在 HTTP 级别还有另一个选项是使用 chunked encoding,您不需要提供 Content-Length 标头,主体本身被编码为自描述数据块。然而不幸的是,许多客户端和服务器(包括httplib)的HTTP 软件倾向于假设只有响应 会被分块,而请求 将始终使用Content-Length。我想这个假设是因为请求通常很小,但是对于POSTPUT,这个假设当然不成立。

    假设您确信您的服务器将处理分块请求,您可以尝试这样做 - 为此,您需要构建一个 StringIO 对象(或任何其他没有 fileno() 方法的对象来击败 @987654352 @ 的自动 Content-Length 插入)已经放置了分块编码,并为您自己的 Transfer-Encoding 标头提供值 chunked。如果您的软件旨在与各种服务器一起使用,我个人不建议您这样做。

    编辑:顺便说一句,如果您使用分块编码,您不得发送Content-Length 标头 - 请参阅HTTP RFC §4.4 第 3 项。通过请求,当然,您不能通过简单地关闭连接来发出正文结束的信号,因为这样您就没有可以接收响应的连接。

    作为对分块请求的支持有多差的一个例子,nginx 仅在去年年底将其添加到version 1.3.9 的核心功能中(尽管在那之前有a plugin for it)。

    编辑 2:

    如果您通读 Wikipedia 文章,您会发现除了发送正确的标头之外还有更多内容 - 您必须将正文拆分为多个块,并在每个块中发送一个包含该块大小的小标头十六进制。这通常会在发送响应时为您完成,但正如我在请求中提到的那样,对它的支持很差。

    这是一个围绕类文件对象的包装器示例,它将主体转换为块。我已经修改了您上面的示例以展示如何使用它,尽管 "hello world" 主体当然很小,最终只能成为一个块。但是,它应该适用于任何大小的物体。它应该适用于具有read() 方法的任何对象,该方法与Python file 对象的工作方式相同。实际上,如果您将标准 Python 文件对象包装在其中之一中,它将阻止 httplib 添加 Content-Length,因为它不支持 len()fileno()

    您仍然需要记住自己添加 Transfer-Encoding 标头,如下面的示例所示:

    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()able 流,所以长度身体不可用。我尝试将Transfer-Encoding: chunked 添加到标题中,但仍然无法发送分块正文:(
    • 您需要做的不仅仅是添加 Transfer-Encoding 标头 - 请参阅我上面的第二个编辑,其中我编写了一个包装器,您可以将它放在您的流周围,它将在适合httplib消费的形式。
    猜你喜欢
    • 1970-01-01
    • 2020-02-11
    • 2022-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多