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é?
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
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'
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)
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
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())
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 :)