2009-03-25 10 views
44

Problema: Al realizar una transferencia de datos con urllib2 de Python, todos los datos se codifican con URL y se envían como Content-Type: application/x-www-form-urlencoded. Al cargar archivos, el tipo de contenido debe establecerse en multipart/form-data y los contenidos deben estar codificados en MIME. Una discusión de este problema está aquí: http://code.activestate.com/recipes/146306/Uso de MultipartPostHandler para POST datos de formulario con Python

Para superar esta limitación algunos codificadores afilados crearon una biblioteca llamada MultipartPostHandler que crea un OpenerDirector puede utilizar con urllib2 a POST su mayoría 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 URL y no tienen codificación MIME multiparte. Por favor, ayúdame a descubrir lo que estoy haciendo mal o una mejor manera de hacerlo. Gracias :-)

FROM_ADDR = '[email protected]' 

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. Conozco la solución ActiveState httplib para esto (me he vinculado a ella más arriba). Prefiero abstraer el problema y usar una cantidad mínima de código para seguir usando urllib2. ¿Alguna idea de por qué el abridor no se está instalando y utilizando?

Respuesta

57

Parece que la forma más fácil y más compatible para evitar este problema es utilizar el módulo de 'cartel'.

# 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 <input> 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ó perfectamente y no tuve que ensuciar con httplib. El módulo está disponible aquí: http://atlee.ca/software/poster/index.html

+1

¡Esto es exactamente lo que necesitaba! Prestigio. –

+1

Sé que esta es una publicación anterior, pero obtengo esto del póster: 'AttributeError: multipart_yielder instance no tiene el atributo '__len __'' preguntándose si alguien más tiene este problema. – Andy

+5

@nalroff No ha llamado 'poster.streaminghttp.register_openers()' –

32

Encontrado esta receta para enviar varias partes utilizando httplib directamente (sin librerías 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' 
+6

Esto parece derecho, pero el módulo cartel es más correcto. – bentford

+3

Creo que este enfoque es más "independiente" porque no requiere módulos nuevos y se puede "compilar" con py2exe para ejecutarse en Windows, como un archivo * .exe. Agregar el módulo 'póster' también está bien, pero no funcionó. Este método fue el mejor para mí. ¡Hurra al autor! – garmoncheg

+1

¿El archivo "value" necesita ser codificado de alguna manera o es solo el puro bytest? –

29

sólo tiene que utilizar python-requests, fijará las cabeceras adecuado y de que subir para usted:

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

¡Gracias, funciona como un encanto! – bszom

+0

+1 excelente, corto y exactamente lo que necesitaba! – Bogatyr

+0

¡Especificar el nombre del campo de entrada html es crítico! Gracias –

0

que lo que un coinciden, hace 2 años, 6 meses creo el proyecto

https://pypi.python.org/pypi/MultipartPostHandler2, que arregla MultipartPostHandler para sistemas utf-8. También hice algunas mejoras menores, puedes probarlo :)

+0

¡Oye, hermano! Creo que el nombre de su paquete elegido me obliga a no verificarlo. – pylover

+0

lo siento, no entendí, no pude modificar MultipartPostHandler, así que tengo que llamarlo MultipartPostHandler2 –

+0

pypi admite múltiples versiones por paquete, si el nombre ya está tomado, debe elegir otro buen nombre de paquete. Pypi es nuestro. Todos somos responsables de lo que hacemos con él – pylover

1

Me encontré con el mismo problema y tuve que hacer una publicación de formulario multiparte sin utilizar bibliotecas externas. Escribí un completo blogpost about the issues I ran into.

Terminé usando una versión modificada de http://code.activestate.com/recipes/146306/. El código en esa url simplemente 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 
0

Para responder a la pregunta de por qué el código original no funcionó de la OP, el manejador pasó no era una instancia de una clase. La línea

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

debe leer

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