Создание и анализ многокомпонентных HTTP-запросов в Python - PullRequest
9 голосов
/ 14 декабря 2010

Я пытаюсь написать некоторый код на Python, который может создавать многокомпонентные http-запросы MIME в клиенте, а затем соответствующим образом интерпретировать их на сервере. Я думаю, что на клиентской стороне это частично удалось:

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, и я не уверен, как они будут связаны с заголовками HTTP, включенными в httplib:

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

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

Это может быть причиной того, что когда этот запрос получен моим приложением web.py, я просто получаю сообщение об ошибке. Обработчик POST web.py:

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 (возможно, он просто не поддерживает многокомпонентные запросы)? Любые подсказки или предложения альтернативных библиотек кода будут с благодарностью приняты.

EDIT

Вышеуказанная ошибка была вызвана тем, что данные не были автоматически закодированы в 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

Так что тут что-то не так.

Спасибо

Richard

Ответы [ 3 ]

1 голос
/ 14 декабря 2010

Я использовал этот пакет от Will Holcomb http://pypi.python.org/pypi/MultipartPostHandler/0.1.0, чтобы делать запросы из нескольких частей с помощью urllib2, он может вам помочь.

1 голос
/ 22 декабря 2010

После небольшого исследования ответ на этот вопрос стал ясен.Короткий ответ заключается в том, что хотя Content-Disposition является необязательным в сообщении с кодировкой Mime, web.py требует его для каждой части mime для правильного анализа HTTP-запроса.* В отличие от других комментариев по этому вопросу, разница между HTTP и электронной почтой не имеет значения, поскольку они являются просто транспортными механизмами для сообщения Mime и не более того.Многокомпонентные / связанные (не multipart / form-data) сообщения распространены при обмене контентом веб-сервисами, что является здесь примером использования.Однако предоставленные фрагменты кода являются точными и привели меня к несколько более короткому решению проблемы.

# 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подходит для создания сообщений Mime для передачи по HTTP, за исключением обработки его заголовка.

Мой другой общий аспект связан с масштабируемостью.Ни это решение, ни другие, предложенные здесь, не масштабируются должным образом, поскольку они читают содержимое файла в переменную, прежде чем объединить его в сообщение MIME.Лучшим решением было бы то, которое могло бы сериализоваться по требованию, поскольку контент передается по HTTP-соединению.Мне не нужно это исправлять, но я вернусь сюда с решением, если доберусь до него.

0 голосов
/ 14 декабря 2010

В вашем запросе есть ряд ошибок. Как предполагает TokenMacGuy, multipart / mixed не используется в HTTP; используйте вместо этого multipart / form-data. Кроме того, части должны иметь заголовок Content-disposition. Фрагмент Python для этого можно найти в Code Recipes .

...