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
使用的形式发出块编码数据。