1

像这样的代码:

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() 发送的数据不包含 headerContent-Length和区域中的任何正文内容body

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

4

1 回答 1

3

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标题,如我在上面的示例中所示。如果这只是一个测试,而您将在现实中使用真实文件,那么httplib只要os.fstat()在您的平台上工作,您就可以依靠正确设置标题。如果没有,您可以随时调用os.stat()文件名并以相同的方式提供您自己的标题。

如果您想同时处理两个真实文件,StringIO那么您可以随时执行以下操作:

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

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

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

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

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

作为对分块请求的支持有多差的一个例子,nginx仅在去年年底的1.3.9 版本中将其添加到核心功能中(尽管在此之前有一个插件)。

编辑2:

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

这是一个围绕类文件对象的包装器示例,它将主体转换为块。我已经修改了你上面的例子来展示如何使用它,虽然"hello world"身体当然很小,它最终只是一个块。但是,它应该适用于任何大小的物体。它应该适用于任何对象,其read()方法与 Python 对象的工作方式相同file。实际上,如果您将标准 Python 文件对象包装在其中之一中,它将阻止httplib添加 a 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"})
于 2013-07-02T10:19:56.257 回答