Python POST datos binarios

Estoy escribiendo un código para interactuar con redmine y necesito cargar algunos archivos como parte del proceso, pero no estoy seguro de cómo hacer una solicitud POST desde Python que contiene un archivo binario.

Estoy tratando de imitar los comandos aquí :

curl --data-binary "@image.png" -H "Content-Type: application/octet-stream" -X POST -u login:password http://redmine/uploads.xml 

En python (abajo), pero no parece funcionar. No estoy seguro de si el problema está relacionado de alguna manera con la encoding del archivo o si hay algún problema con los encabezados.

 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 

Tengo acceso al servidor y parece un error de encoding:

 ... 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) 

Básicamente lo que haces es correcto. Al mirar los documentos de redmine a los que se vinculó, parece que el sufijo después del punto en la URL denota el tipo de datos publicados (.json para JSON, .xml para XML), que concuerda con la respuesta que obtiene: Processing by AttachmentsController#upload as XML . Supongo que tal vez haya un error en los documentos y para publicar datos binarios debería intentar usar http://redmine/uploads url en lugar de http://sofes.miximages.com/python/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

ACTUALIZAR

Para averiguar por qué esto funciona con las solicitudes pero no con urllib2, tenemos que examinar la diferencia en lo que se está enviando. Para ver esto, estoy enviando tráfico a http proxy (Fiddler) que se ejecuta en el puerto 8888:

Usando Solicitudes

 import requests data = 'test data' res = requests.post(url='http://localhost:8888', data=data, headers={'Content-Type': 'application/octet-stream'}) 

vemos

 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 

y usando 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) 

obtenemos

 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 

No veo ninguna diferencia que justifique un comportamiento diferente que usted observe. Dicho esto, no es infrecuente que los servidores http inspeccionen el encabezado User-Agent y varíen el comportamiento según su valor. Intente cambiar los encabezados enviados por Solicitudes uno por uno, haciéndolos iguales a los que envía urllib2 y vea cuándo deja de funcionar.

Esto no tiene nada que ver con una carga mal formada. El error HTTP especifica claramente 401 no autorizados, y le dice que el token CSRF no es válido. Intente enviar un token CSRF válido con la carga.

Más sobre los tokens csrf aquí:

¿Qué es un token CSRF? ¿Cuál es su importancia y cómo funciona?

es necesario agregar el encabezado Content-Disposition, algo así (aunque yo usé mod-python aquí, pero el principio debería ser el mismo):

 request.headers_out['Content-Disposition'] = 'attachment; filename=%s' % myfname 

Puede utilizar unirest , proporciona un método fácil para publicar la solicitud. `

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

`

Puede consultar el ejemplo completo en http://stackandqueue.com/?p=57