J'écris du code pour faire l'interface avec Redmine et j'ai besoin de télécharger des fichiers dans le cadre du processus, mais je ne sais pas comment faire une demande de POST de python contenant un fichier binaire.
J'essaie d'imiter les commandes ici :
curl --data-binary "@image.png" -H "Content-Type: application/octet-stream" -X POST -u login:password http://redmine/uploads.xml
Dans python (ci-dessous), mais cela ne semble pas fonctionner. Je ne sais pas si le problème est lié au codage du fichier ou si quelque chose ne va pas avec les en-têtes.
import urllib2, os
FilePath = "C:\somefolder\somefile.7z"
FileData = open(FilePath, "rb")
length = os.path.getsize(FilePath)
password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
password_manager.add_password(None, 'http://redmine/', 'admin', 'admin')
auth_handler = urllib2.HTTPBasicAuthHandler(password_manager)
opener = urllib2.build_opener(auth_handler)
urllib2.install_opener(opener)
request = urllib2.Request( r'http://redmine/uploads.xml', FileData)
request.add_header('Content-Length', '%d' % length)
request.add_header('Content-Type', 'application/octet-stream')
try:
response = urllib2.urlopen( request)
print response.read()
except urllib2.HTTPError as e:
error_message = e.read()
print error_message
J'ai accès au serveur et cela ressemble à une erreur d'encodage:
...
invalid byte sequence in UTF-8
Line: 1
Position: 624
Last 80 unconsumed characters:
7z¼¯'ÅÐз2^Ôøë4g¸R<süðí6kĤª¶!»=}jcdjSPúá-º#»ÄAtD»H7Ê!æ½]j):
(further down)
Started POST "/uploads.xml" for 192.168.0.117 at 2013-01-16 09:57:49 -0800
Processing by AttachmentsController#upload as XML
WARNING: Can't verify CSRF token authenticity
Current user: anonymous
Filter chain halted as :authorize_global rendered or redirected
Completed 401 Unauthorized in 13ms (ActiveRecord: 3.1ms)
Fondamentalement, ce que vous faites est correct. En regardant les documents redmine auxquels vous avez lié, il semble que le suffixe après le point dans l'URL indique le type de données postées (.json pour JSON, .xml pour XML), ce qui est en accord avec la réponse que vous obtenez - Processing by AttachmentsController#upload as XML
. Je suppose qu’il ya peut-être un bogue dans la documentation et que pour publier des données binaires, vous devriez essayer d’utiliser http://redmine/uploads
url au lieu de http://redmine/uploads.xml
.
Btw, je recommande fortement très bon et très populaire Requests bibliothèque pour http en Python. C'est beaucoup mieux que ce qui est dans la bibliothèque standard (urllib2). Il prend également en charge l'authentification, mais je l'ai ignoré par souci de concision.
import requests
data = open('./x.png', 'rb').read()
res = requests.post(url='http://httpbin.org/post',
data=data,
headers={'Content-Type': 'application/octet-stream'})
# let's check if what we sent is what we intended to send...
import json
import base64
assert base64.b64decode(res.json()['data'][len('data:application/octet-stream;base64,'):]) == data
MISE À JOUR
Pour savoir pourquoi cela fonctionne avec Requests mais pas avec urllib2, nous devons examiner la différence entre ce qui est envoyé. Pour voir ceci, j'envoie du trafic au proxy http (Fiddler) s'exécutant sur le port 8888:
Utiliser les demandes
import requests
data = 'test data'
res = requests.post(url='http://localhost:8888',
data=data,
headers={'Content-Type': 'application/octet-stream'})
nous voyons
POST http://localhost:8888/ HTTP/1.1
Host: localhost:8888
Content-Length: 9
Content-Type: application/octet-stream
Accept-Encoding: gzip, deflate, compress
Accept: */*
User-Agent: python-requests/1.0.4 CPython/2.7.3 Windows/Vista
test data
et en utilisant urllib2
import urllib2
data = 'test data'
req = urllib2.Request('http://localhost:8888', data)
req.add_header('Content-Length', '%d' % len(data))
req.add_header('Content-Type', 'application/octet-stream')
res = urllib2.urlopen(req)
on a
POST http://localhost:8888/ HTTP/1.1
Accept-Encoding: identity
Content-Length: 9
Host: localhost:8888
Content-Type: application/octet-stream
Connection: close
User-Agent: Python-urllib/2.7
test data
Je ne vois aucune différence qui justifierait un comportement différent de celui que vous observez. Cela dit, il n’est pas rare que les serveurs http inspectent User-Agent
en-tête et modifie le comportement en fonction de sa valeur. Essayez de changer les en-têtes envoyés par les requêtes une par une en les rendant identiques à ceux envoyés par urllib2 et de voir quand cela cesse de fonctionner.
Cela n'a rien à voir avec un téléchargement mal formé. L'erreur HTTP indique clairement 401 non autorisé et vous indique que le jeton CSRF n'est pas valide. Essayez d'envoyer un jeton CSRF valide avec le téléchargement.
Plus d'informations sur les jetons csrf ici:
Qu'est-ce qu'un jeton CSRF? Quelle est son importance et comment fonctionne-t-il?
Vous pouvez utiliser nirest , il fournit une méthode simple pour poster une demande. `
import unirest
def callback(response):
print "code:"+ str(response.code)
print "******************"
print "headers:"+ str(response.headers)
print "******************"
print "body:"+ str(response.body)
print "******************"
print "raw_body:"+ str(response.raw_body)
# consume async post request
def consumePOSTRequestASync():
params = {'test1':'param1','test2':'param2'}
# we need to pass a dummy variable which is open method
# actually unirest does not provide variable to shift between
# application-x-www-form-urlencoded and
# multipart/form-data
params['dummy'] = open('dummy.txt', 'r')
url = 'http://httpbin.org/post'
headers = {"Accept": "application/json"}
# call get service with headers and params
unirest.post(url, headers = headers,params = params, callback = callback)
# post async request multipart/form-data
consumePOSTRequestASync()
`
Vous pouvez vérifier l'exemple complet à l'adresse http://stackandqueue.com/?p=57
vous devez ajouter un en-tête Content-Disposition, comme ceci (bien que j'utilise mod-python ici, mais le principe devrait être le même):
request.headers_out['Content-Disposition'] = 'attachment; filename=%s' % myfname