Uso de MultipartPostHandler para POST datos de formulario con Python

Problema: al publicar datos con urllib2 de Python, todos los datos se codifican en la URL y se envían como Content-Type: application / x-www-form-urlencoded. Al cargar archivos, el tipo de contenido debe configurarse en multipart / form-data y el contenido debe estar codificado en MIME. Una discusión de este problema está aquí: http://code.activestate.com/recipes/146306/

Para evitar esta limitación, algunos progtwigdores nítidos crearon una biblioteca llamada MultipartPostHandler que crea un OpenerDirector que puede usar con urllib2 para realizar POST de forma automática con multipart / form-data. Una copia de esta biblioteca está aquí: http://peerit.blogspot.com/2007/07/multipartposthandler-doesnt-work-for.html

Soy nuevo en Python y no puedo hacer que esta biblioteca funcione. Escribí esencialmente el siguiente código. Cuando lo capturo en un proxy HTTP local, puedo ver que los datos aún están codificados en la URL y no codificados MIME en varias partes. Por favor, ayúdame a descubrir qué estoy haciendo mal o una mejor manera de hacer esto. Gracias 🙂

FROM_ADDR = 'my@email.com' try: data = open(file, 'rb').read() except: print "Error: could not open file %s for reading" % file print "Check permissions on the file or folder it resides in" sys.exit(1) # Build the POST request url = "http://somedomain.com/?action=analyze" post_data = {} post_data['analysisType'] = 'file' post_data['executable'] = data post_data['notification'] = 'email' post_data['email'] = FROM_ADDR # MIME encode the POST payload opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler) urllib2.install_opener(opener) request = urllib2.Request(url, post_data) request.set_proxy('127.0.0.1:8080', 'http') # For testing with Burp Proxy # Make the request and capture the response try: response = urllib2.urlopen(request) print response.geturl() except urllib2.URLError, e: print "File upload failed..." 

EDIT1: Gracias por su respuesta. Soy consciente de la solución ActiveState httplib para esto (me he vinculado a él anteriormente). Prefiero abstraer el problema y usar una cantidad mínima de código para continuar usando urllib2 como lo he hecho. ¿Alguna idea de por qué el abridor no está siendo instalado y usado?

Parece que la forma más fácil y más compatible de solucionar este problema es usar el módulo ‘póster’.

 # test_client.py from poster.encode import multipart_encode from poster.streaminghttp import register_openers import urllib2 # Register the streaming http handlers with urllib2 register_openers() # Start the multipart/form-data encoding of the file "DSC0001.jpg" # "image1" is the name of the parameter, which is normally set # via the "name" parameter of the HTML  tag. # headers contains the necessary Content-Type and Content-Length # datagen is a generator object that yields the encoded parameters datagen, headers = multipart_encode({"image1": open("DSC0001.jpg")}) # Create the Request object request = urllib2.Request("http://localhost:5000/upload_image", datagen, headers) # Actually do the request, and get the response print urllib2.urlopen(request).read() 

Esto funcionó perfecto y no tuve que muck con httplib. El módulo está disponible aquí: http://atlee.ca/software/poster/index.html

Encontré esta receta para publicar httplib partes usando httplib directamente (no hay bibliotecas externas involucradas)

 import httplib import mimetypes def post_multipart(host, selector, fields, files): content_type, body = encode_multipart_formdata(fields, files) h = httplib.HTTP(host) h.putrequest('POST', selector) h.putheader('content-type', content_type) h.putheader('content-length', str(len(body))) h.endheaders() h.send(body) errcode, errmsg, headers = h.getreply() return h.file.read() def encode_multipart_formdata(fields, files): LIMIT = '----------lImIt_of_THE_fIle_eW_$' CRLF = '\r\n' L = [] for (key, value) in fields: L.append('--' + LIMIT) L.append('Content-Disposition: form-data; name="%s"' % key) L.append('') L.append(value) for (key, filename, value) in files: L.append('--' + LIMIT) L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)) L.append('Content-Type: %s' % get_content_type(filename)) L.append('') L.append(value) L.append('--' + LIMIT + '--') L.append('') body = CRLF.join(L) content_type = 'multipart/form-data; boundary=%s' % LIMIT return content_type, body def get_content_type(filename): return mimetypes.guess_type(filename)[0] or 'application/octet-stream' 

Simplemente use las solicitudes de python , establecerá los encabezados adecuados y los cargará por usted:

 import requests files = {"form_input_field_name": open("filename", "rb")} requests.post("http://httpbin.org/post", files=files) 

Me encontré con el mismo problema y necesitaba hacer una publicación de formulario de varias partes sin usar bibliotecas externas. Escribí todo un blogpost sobre los problemas que encontré .

Terminé usando una versión modificada de http://code.activestate.com/recipes/146306/ . El código en ese url en realidad solo agrega el contenido del archivo como una cadena, lo que puede causar problemas con los archivos binarios. Aquí está mi código de trabajo.

 import mimetools import mimetypes import io import http import json form = MultiPartForm() form.add_field("form_field", "my awesome data") # Add a fake file form.add_file(key, os.path.basename(filepath), fileHandle=codecs.open("/path/to/my/file.zip", "rb")) # Build the request url = "http://www.example.com/endpoint" schema, netloc, url, params, query, fragments = urlparse.urlparse(url) try: form_buffer = form.get_binary().getvalue() http = httplib.HTTPConnection(netloc) http.connect() http.putrequest("POST", url) http.putheader('Content-type',form.get_content_type()) http.putheader('Content-length', str(len(form_buffer))) http.endheaders() http.send(form_buffer) except socket.error, e: raise SystemExit(1) r = http.getresponse() if r.status == 200: return json.loads(r.read()) else: print('Upload failed (%s): %s' % (r.status, r.reason)) class MultiPartForm(object): """Accumulate the data to be used when posting a form.""" def __init__(self): self.form_fields = [] self.files = [] self.boundary = mimetools.choose_boundary() return def get_content_type(self): return 'multipart/form-data; boundary=%s' % self.boundary def add_field(self, name, value): """Add a simple field to the form data.""" self.form_fields.append((name, value)) return def add_file(self, fieldname, filename, fileHandle, mimetype=None): """Add a file to be uploaded.""" body = fileHandle.read() if mimetype is None: mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' self.files.append((fieldname, filename, mimetype, body)) return def get_binary(self): """Return a binary buffer containing the form data, including attached files.""" part_boundary = '--' + self.boundary binary = io.BytesIO() needsCLRF = False # Add the form fields for name, value in self.form_fields: if needsCLRF: binary.write('\r\n') needsCLRF = True block = [part_boundary, 'Content-Disposition: form-data; name="%s"' % name, '', value ] binary.write('\r\n'.join(block)) # Add the files to upload for field_name, filename, content_type, body in self.files: if needsCLRF: binary.write('\r\n') needsCLRF = True block = [part_boundary, str('Content-Disposition: file; name="%s"; filename="%s"' % \ (field_name, filename)), 'Content-Type: %s' % content_type, '' ] binary.write('\r\n'.join(block)) binary.write('\r\n') binary.write(body) # add closing boundary marker, binary.write('\r\n--' + self.boundary + '--\r\n') return binary 

Yo que coincidencia, hace 2 años, 6 meses creo el proyecto.

https://pypi.python.org/pypi/MultipartPostHandler2 , que corrige MultipartPostHandler para los sistemas utf-8. También he hecho algunas mejoras menores, le invitamos a probarlo 🙂

Para responder a la pregunta del OP sobre por qué el código original no funcionó, el controlador que se aprobó no era una instancia de una clase. La línea

 # MIME encode the POST payload opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler) 

tiene que leer

 opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler())