2013-01-16 16 views
27

Sto scrivendo del codice per interfacciare con redmine e ho bisogno di caricare alcuni file come parte del processo, ma non sono sicuro di come eseguire una richiesta POST da python contenente un file binario.Dati binari POST Python

sto cercando di imitare i comandi here:

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

in Python (di seguito), ma non sembra funzionare. Non sono sicuro che il problema sia in qualche modo correlato alla codifica del file o se qualcosa non va nelle intestazioni.

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 

ho accesso al server e si presenta come un errore di codifica:

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

risposta

35

Fondamentalmente quello che fai è corretto. Guardando i documenti redmine a cui sei collegato, sembra che il suffisso dopo il punto nell'URL denoti il ​​tipo di dati pubblicati (.json per JSON, .xml per XML), che concorda con la risposta che ottieni - Processing by AttachmentsController#upload as XML. Suppongo che ci sia un errore nei documenti e di pubblicare dati binari dovresti provare a utilizzare l'URL http://redmine/uploads anziché http://redmine/uploads.xml.

Btw, mi raccomando molto bene e molto popolare libreria Requests per http in Python. È molto meglio di ciò che è nella lib standard (urllib2). Supporta anche l'autenticazione, ma l'ho saltato per brevità qui.

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 

UPDATE

per scoprire perché questo funziona con le richieste, ma non con urllib2 dobbiamo esaminare la differenza tra ciò che viene inviato. Per vedere questo sto inviando traffico al proxy HTTP (Fiddler) in esecuzione sulla porta 8888:

utilizzando le richieste

import requests 

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

vediamo

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 

e utilizzando 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) 

otteniamo

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 

Non vedo alcuna differenza che giustifichi un comportamento diverso da te osservato. Detto questo, non è raro che i server http possano ispezionare l'intestazione User-Agent e variare il comportamento in base al suo valore. Prova a cambiare le intestazioni inviate dalle richieste una ad una, rendendole uguali a quelle inviate da urllib2 e vedere quando smette di funzionare.

+0

Non ho idea del perché, ma usando il modulo richieste lo stesso codice funziona perfettamente ... Grazie mille. Anche se ora sono molto curioso di sapere perché urllib non funziona ... – Mac

0

è necessario aggiungere intestazione Content-Disposition, smth come questo (anche se ho usato mod-python qui, ma principio dovrebbe essere lo stesso):

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

ricciolo non ha bisogno di questo, il motivo per cui Python fa? – Mac

+0

Penso che il ricciolo lo stia facendo silenziosamente, anche se non scommetterei la fattoria su questo - la tua opzione pratica è usare Wireshark e vedere semplicemente cosa sta succedendo sul filo tra curl e server (non è facile usare wireshark su localhost però, dovresti avere una macchina separata per quello). – mrkafk

+0

Apparentemente no: http://pastie.org/private/w4qrctt9t8rx2dzkrg319g#5,15 – Mac

0

È possibile utilizzare unirest, e fornisce un facile metodo per inviare richiesta. `

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

`

È possibile controllare esempio completo in http://stackandqueue.com/?p=57