在Python中创建和解析多部分HTTP请求

时间:2010-12-13 22:37:56

标签: python http mime multipart

我正在尝试编写一些python代码,可以在客户端创建多部分mime http请求,然后在服务器上进行适当的解释。我认为,我已经在客户端部分取得了成功:

from email.mime.multipart import MIMEMultipart, MIMEBase
import httplib
h1 = httplib.HTTPConnection('localhost:8080')
msg = MIMEMultipart()
fp = open('myfile.zip', 'rb')
base = MIMEBase("application", "octet-stream")
base.set_payload(fp.read())
msg.attach(base)
h1.request("POST", "http://localhost:8080/server", msg.as_string())

唯一的问题是电子邮件库还包含Content-Type和MIME-Version标头,我不确定它们将如何与httplib包含的HTTP标头相关:

Content-Type: multipart/mixed; boundary="===============2050792481=="
MIME-Version: 1.0

--===============2050792481==
Content-Type: application/octet-stream
MIME-Version: 1.0

这可能是我的web.py应用程序收到此请求时的原因,我只是收到错误消息。 web.py POST处理程序:

class MultipartServer:
    def POST(self, collection):
        print web.input()

引发此错误:

Traceback (most recent call last):
  File "/usr/local/lib/python2.6/dist-packages/web.py-0.34-py2.6.egg/web/application.py", line 242, in process
    return self.handle()
  File "/usr/local/lib/python2.6/dist-packages/web.py-0.34-py2.6.egg/web/application.py", line 233, in handle
    return self._delegate(fn, self.fvars, args)
  File "/usr/local/lib/python2.6/dist-packages/web.py-0.34-py2.6.egg/web/application.py", line 415, in _delegate
    return handle_class(cls)
  File "/usr/local/lib/python2.6/dist-packages/web.py-0.34-py2.6.egg/web/application.py", line 390, in handle_class
    return tocall(*args)
  File "/home/richard/Development/server/webservice.py", line 31, in POST
    print web.input()
  File "/usr/local/lib/python2.6/dist-packages/web.py-0.34-py2.6.egg/web/webapi.py", line 279, in input
    return storify(out, *requireds, **defaults)
  File "/usr/local/lib/python2.6/dist-packages/web.py-0.34-py2.6.egg/web/utils.py", line 150, in storify
    value = getvalue(value)
  File "/usr/local/lib/python2.6/dist-packages/web.py-0.34-py2.6.egg/web/utils.py", line 139, in getvalue
    return unicodify(x)
  File "/usr/local/lib/python2.6/dist-packages/web.py-0.34-py2.6.egg/web/utils.py", line 130, in unicodify
    if _unicode and isinstance(s, str): return safeunicode(s)
  File "/usr/local/lib/python2.6/dist-packages/web.py-0.34-py2.6.egg/web/utils.py", line 326, in safeunicode
    return obj.decode(encoding)
  File "/usr/lib/python2.6/encodings/utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
UnicodeDecodeError: 'utf8' codec can't decode bytes in position 137-138: invalid data

我的代码行由误差线表示大约一半:

  File "/home/richard/Development/server/webservice.py", line 31, in POST
    print web.input()

它即将到来,但我不确定从哪里开始。这是我的客户端代码的问题,还是web.py的限制(也许它只是不支持多部分请求)?将非常感激地收到替代代码库的任何提示或建议。

修改

上述错误是由于数据未自动进行base64编码引起的。添加

encoders.encode_base64(base)

摆脱这个错误,现在问题很明显。在服务器中没有正确解释HTTP请求,可能是因为电子邮件库包含了身体中应该是HTTP标头的内容:

<Storage {'Content-Type: multipart/mixed': u'', 
          ' boundary': u'"===============1342637378=="\n'
          'MIME-Version: 1.0\n\n--===============1342637378==\n'
          'Content-Type: application/octet-stream\n'
          'MIME-Version: 1.0\n' 
          'Content-Transfer-Encoding: base64\n'
          '\n0fINCs PBk1jAAAAAAAAA.... etc

所以有些东西不对。

由于

理查德

3 个答案:

答案 0 :(得分:1)

我使用Will Holcomb http://pypi.python.org/pypi/MultipartPostHandler/0.1.0的这个包来用urllib2发出多部分请求,它可以帮助你。

答案 1 :(得分:1)

经过一番探索,这个问题的答案已经变得清晰了。简短的回答是,尽管Mime编码的消息中有Content-Disposition is optional,但web.py需要为每个mime-part提供它,以便正确解析HTTP请求。

与此问题的其他评论相反,HTTP和电子邮件之间的区别是无关紧要的,因为它们只是Mime消息的传输机制,仅此而已。多部分/相关(非多部分/表单数据)消息在内容交换Web服务中很常见,这是此处的用例。但是,提供的代码片段是准确的,并使我能够更简单地解决问题。

# open an HTTP connection
h1 = httplib.HTTPConnection('localhost:8080')

# create a mime multipart message of type multipart/related
msg = MIMEMultipart("related")

# create a mime-part containing a zip file, with a Content-Disposition header
# on the section
fp = open('file.zip', 'rb')
base = MIMEBase("application", "zip")
base['Content-Disposition'] = 'file; name="package"; filename="file.zip"'
base.set_payload(fp.read())
encoders.encode_base64(base)
msg.attach(base)

# Here's a rubbish bit: chomp through the header rows, until hitting a newline on
# its own, and read each string on the way as an HTTP header, and reading the rest
# of the message into a new variable
header_mode = True
headers = {}
body = []
for line in msg.as_string().splitlines(True):
    if line == "\n" and header_mode == True:
        header_mode = False
    if header_mode:
        (key, value) = line.split(":", 1)
        headers[key.strip()] = value.strip()
    else:
        body.append(line)
body = "".join(body)

# do the request, with the separated headers and body
h1.request("POST", "http://localhost:8080/server", body, headers)

这很好地被web.py所接受,所以很明显email.mime.multipart适合创建由HTTP传输的Mime消息,但其头处理除外。

我的另一个整体目标是可扩展性。这个解决方案和这里提出的其他解决方案都没有很好地扩展,因为它们在捆绑mime消息之前将文件的内容读入变量。一个更好的解决方案是,当内容通过HTTP连接传出时,可以按需串行化。对我来说,解决这个问题并不紧迫,但如果我做到这一点,我会带着解决方案回到这里。

答案 2 :(得分:0)

您的请求有很多问题。正如TokenMacGuy所说,在HTTP中未使用multipart / mixed;请改用multipart / form-data。此外,部件应具有Content-disposition标头。可以在Code Recipes中找到要执行此操作的python片段。