Crea y analiza solicitudes HTTP multiparte en Python

Estoy tratando de escribir algún código de Python que pueda crear solicitudes http de mime de varias partes en el cliente y luego interpretarlas adecuadamente en el servidor. Creo que, parcialmente, he tenido éxito en el final del cliente con esto:

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()) 

El único problema con esto es que la biblioteca de correo electrónico también incluye los encabezados Content-Type y MIME-Version, y no estoy seguro de cómo estarán relacionados con los encabezados HTTP incluidos por httplib:

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

Esta puede ser la razón por la que cuando mi solicitud web.py recibe esta solicitud, acabo de recibir un mensaje de error. El controlador web.py POST:

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

Lanza este error:

 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 

Mi línea de código está representada por la línea de error aproximadamente a la mitad:

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

    Viene a lo largo, pero no estoy seguro de a dónde ir desde aquí. ¿Es este un problema con el código de mi cliente o una limitación de web.py (tal vez simplemente no puede admitir solicitudes de varias partes)? Cualquier sugerencia o sugerencia de bibliotecas de códigos alternativos será recibida con gratitud.

    EDITAR

    El error anterior se debió a que los datos no se codificaron en base64 automáticamente. Añadiendo

     encoders.encode_base64(base) 

    Se deshace de este error, y ahora el problema está claro. La solicitud HTTP no se interpreta correctamente en el servidor, presumiblemente porque la biblioteca de correo electrónico incluye lo que deberían ser los encabezados HTTP en el cuerpo:

     <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 

    Así que algo no está justo ahí.

    Gracias

    Ricardo

    Utilicé este paquete de Will Holcomb http://pypi.python.org/pypi/MultipartPostHandler/0.1.0 para realizar solicitudes de varias partes con urllib2, puede que te ayude.

    Después de un poco de exploración, la respuesta a esta pregunta se ha vuelto clara. La respuesta corta es que aunque la Disposición de contenido es opcional en un mensaje codificado con Mime, web.py lo requiere para cada parte de mime para analizar correctamente la solicitud HTTP.

    Al contrario de otros comentarios sobre esta pregunta, la diferencia entre HTTP y correo electrónico es irrelevante, ya que son simplemente mecanismos de transporte para el mensaje Mime y nada más. Los mensajes multiparte / relacionados (no multipart / form-data) son comunes en los servicios web de intercambio de contenido, que es el caso de uso aquí. Sin embargo, los fragmentos de código proporcionados son precisos y me llevaron a una solución al problema un poco más breve.

     # 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 ha recogido esto perfectamente bien, por lo que está claro que email.mime.multipart es adecuado para crear mensajes Mime para ser transportados por HTTP, con la excepción de su manejo del encabezado.

    Mi otra preocupación general es la escalabilidad. Ni esta solución ni las otras propuestas aquí se escalan bien, ya que leen el contenido de un archivo en una variable antes de agruparse en el mensaje mime. Una solución mejor sería una que pudiera serializarse a pedido, ya que el contenido se canaliza a través de la conexión HTTP. No es urgente para mí arreglar eso, pero volveré aquí con una solución si lo consigo.

    Hay una serie de cosas mal con su solicitud. Como sugiere TokenMacGuy, multipart / mixed no se utiliza en HTTP; use multipart / form-data en su lugar. Además, las partes deben tener un encabezado de disposición de contenido. Un fragmento de python para hacer que se puede encontrar en las Recetas de Código .