2010-12-13 25 views
9

Estoy tratando de escribir algún código python que pueda crear solicitudes http mime de varias partes en el cliente, y luego interpretarlas adecuadamente en el servidor. Tengo, creo que, en parte logrado en el cliente final con esto:Crear y analizar solicitudes HTTP de múltiples partes en Python

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 el tipo de contenido y las cabeceras MIME-Version, y no estoy seguro cómo van a estar relacionados con las cabeceras HTTP incluidos por httplib:

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

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

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

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á representado por la línea de error a mitad de camino:

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

Está quedando , pero no estoy seguro de a dónde ir desde aquí. ¿Es esto un problema con mi código de cliente o una limitación de web.py (quizás simplemente no puede admitir solicitudes de varias partes)? Cualquier sugerencia o sugerencia de bibliotecas de códigos alternativos sería gratamente recibida.

EDITAR

El error anterior fue causado por los datos no siendo automáticamente Base64 codificados. Agregar

encoders.encode_base64(base) 

Deshace este error y ahora el problema es claro. solicitud HTTP no se interpreta correctamente en el servidor, presumiblemente debido a la biblioteca de correo electrónico está incluyendo cuáles deben ser las cabeceras HTTP en el cuerpo en su lugar:

<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á bien allí.

Gracias

Richard

+0

¿Qué diablos es una solicitud http multiparte? ¿Este concepto realmente se usa? – SingleNegationElimination

+0

@TokenMacGuy - sí. sí lo es. –

Respuesta

1

Después de un poco de exploración, la respuesta a esta pregunta se ha aclarado. La respuesta corta es que, aunque el Content-Disposition is optional en un mensaje codificado con Mime, web.py lo requiere para cada parte mime para analizar correctamente la solicitud HTTP.

Contrariamente a 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 multipart/related (no multipart/form-data) son comunes en el intercambio de contenido de los servicios web, que es el caso de uso aquí. Los fragmentos de código proporcionados son precisos, sin embargo, y me llevaron a una solución un poco más breve para el problema.

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

Este es recogido perfectamente bien por web.py, así que está claro que email.mime.multipart es adecuado para la creación de mensajes MIME para ser transportado por HTTP, con la excepción de su manejo cabecera.

Mi otro conern general es la escalabilidad. Ni esta solución ni las otras propuestas aquí se escalan bien, ya que leen los contenidos de un archivo en una variable antes de agruparse en el mensaje de mímica. Una mejor solución sería una que podría 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 consigo hacerlo.

+0

1. Creo que la forma preferida de configurar el encabezado es algo como 'base.add_header ('Content-Disposition', 'file', name = 'package', ... .) '. 2. Es mejor buscar '\ n \ n' (y también' \ r \ n \ r \ n', con por ejemplo 're.search ('\ r? \ N \ r? \ N', ...) ') para que no tenga que dividirse y unirse al cuerpo. 3. Las líneas de encabezado se pueden doblar. 4. Técnicamente, el '\ n' que termina los encabezados no es parte del cuerpo, aunque esto no es dañino. 5. No estoy del todo seguro de que las gramáticas RFC 5322 y RFC 2316 sean 100% compatibles (en particular "caracteres" WRT contra octetos). –

0

Hay un número de cosas mal con su solicitud. Como TokenMacGuy sugiere, multipart/mixed no se usa en HTTP; use multipart/form-data en su lugar. Además, las partes deben tener un encabezado de disposición de contenido. Se puede encontrar un fragmento de pitón en Code Recipes.

+0

gracias - como puede ver, todavía estoy trabajando en esto; Aún no he descubierto dónde se está configurando multipart/mixed. Del mismo modo, aún no he empleado el encabezado Content-Disposition ya que todavía estoy trabajando para que se convierta en una solicitud HTTP en primer lugar. Mi pregunta es sobre cómo construir tal solicitud en primer lugar. Cheers, R. –

+0

Ver la receta. Olvídate de email.mime - HTTP no es correo electrónico. –

+0

Hola Martin; FYI He demostrado que la diferencia entre HTTP y correo electrónico es irrelevante aquí; simplemente son transportes, y el mimo es el mismo en ambos casos. Ver mi respuesta alternativa. Gracias por los consejos. R –

Cuestiones relacionadas