web-dev-qa-db-fra.com

Utilisation de MultipartPostHandler pour POST données de formulaire avec Python

Problème: lors de la publication de données avec urllib2 de Python, toutes les données sont encodées en URL et envoyées sous la forme Content-Type: application/x-www-form-urlencoded. Lors du téléchargement de fichiers, le type de contenu doit plutôt être défini sur multipart/form-data et le contenu doit être codé MIME. Une discussion de ce problème se trouve ici: http://code.activestate.com/recipes/146306/

Pour contourner cette limitation, certains codeurs pointus ont créé une bibliothèque appelée MultipartPostHandler, qui crée un OpenerDirector que vous pouvez utiliser avec urllib2 pour la plupart du temps automatiquement POST avec multipart/form-data. Une copie de cette bibliothèque est ici: http://peerit.blogspot.com/2007/07/multipartposthandler-doesnt-work.for.html

Je suis nouveau sur Python et je ne parviens pas à faire fonctionner cette bibliothèque. J'ai écrit essentiellement le code suivant. Lorsque je le capture dans un proxy HTTP local, je constate que les données sont toujours encodées en URL et non en MIME en plusieurs parties. S'il vous plaît, aidez-moi à comprendre ce que je fais de mal ou une meilleure façon de le faire. Merci :-)

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: Merci pour votre réponse. Je suis au courant de la solution ActiveState httplib à cela (je l'ai lié ci-dessus). Je préfère résumer le problème et utiliser un minimum de code pour continuer à utiliser urllib2 comme je l'ai été. Avez-vous une idée de la raison pour laquelle l'ouvre-porte n'est pas installé ni utilisé?

45
Dan

Il semble que le moyen le plus simple et le plus compatible de résoudre ce problème consiste à utiliser le module "affiche".

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

Cela a fonctionné parfaitement et je n'ai pas eu à muck avec httplib. Le module est disponible ici: http://atlee.ca/software/poster/index.html

57
Dan

Trouvé cette recette pour publier en plusieurs parties en utilisant directement httplib (aucune bibliothèque externe impliquée)

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'
37
nosklo

Utilisez simplement python-request , il définira les en-têtes appropriés et les téléchargera pour vous:

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

J'ai rencontré le même problème et je devais rédiger un message en plusieurs parties sans utiliser de bibliothèques externes. J'ai écrit tout un blogpost sur les problèmes rencontrés }.

J'ai fini par utiliser une version modifiée de http://code.activestate.com/recipes/146306/ . En fait, le code de cette URL ajoute simplement le contenu du fichier sous forme de chaîne, ce qui peut entraîner des problèmes avec les fichiers binaires. Voici mon code de travail.

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
1
Jason Kulatunga

Pour répondre à la question de l'op qui demandait pourquoi le code original ne fonctionnait pas, le gestionnaire transmis n'était pas une instance d'une classe. La ligne

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

devrais lire

opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler())
0
Daryl Tester

Je coïncide il y a 2 ans et 6 mois, je crée le projet 

https://pypi.python.org/pypi/MultipartPostHandler2 , qui corrige MultipartPostHandler pour les systèmes utf-8. J'ai également apporté quelques améliorations mineures, n'hésitez pas à le tester :) 

0
Sérgio