2013-01-16 25 views
27

Piszę kod do interfejsu z redmine i muszę przesłać niektóre pliki w ramach procesu, ale nie jestem pewien, jak wykonać żądanie POST z Pythona zawierające plik binarny.Python POST dane binarne

Próbuję naśladować polecenia here:

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

W python (poniżej), ale nie wydają się działać. Nie jestem pewien, czy problem jest w jakiś sposób związany z kodowaniem pliku lub jeśli coś jest nie tak z nagłówkami.

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 

Mam dostęp do serwera i wygląda błędu kodowania:

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

Odpowiedz

35

Zasadniczo to, co robisz, jest poprawne. Patrząc na dokumenty redmine, z którymi się łączyłeś, wydaje się, że sufiks po kropce w url oznacza typ opublikowanych danych (.json dla JSON, .xml dla XML), który zgadza się z odpowiedzią, którą otrzymujesz - Processing by AttachmentsController#upload as XML. Prawdopodobnie jest jakiś błąd w dokumentach i dane binarne, które powinieneś wypróbować używając adresu URL http://redmine/uploads zamiast http://redmine/uploads.xml.

Btw, bardzo polecam bardzo dobrą i bardzo popularną bibliotekę Requests dla http w Pythonie. Jest znacznie lepszy niż w standardowej bibliotece lib (urllib2). Obsługuje również uwierzytelnianie, ale pominąłem tutaj dla zwięzłości tutaj.

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

Aby dowiedzieć się, dlaczego to działa, ale nie z wnioskami z urllib2 musimy zbadać różnicę, co jest wysłana. Aby to wyślę ruch do serwera proxy HTTP (Skrzypek) działa na porcie 8888 zobacz:

Korzystanie Wnioski

import requests 

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

widzimy

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 

i przy użyciu 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) 

otrzymujemy

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 

Nie widzę żadnych różnic, które uzasadniałyby inne zachowanie, które obserwujesz. Powiedział, że nie jest niczym niezwykłym, że serwery http sprawdzają nagłówek User-Agent i różnią się zachowaniem w zależności od jego wartości. Spróbuj zmienić nagłówki wysłane przez Żądania jeden po drugim, czyniąc je tymi samymi, co wysyłane przez urllib2 i sprawdzając, kiedy przestają działać.

+0

Nie mam pojęcia dlaczego, ale używając modułu żądania, ten sam kod działa dobrze ... Wielkie dzięki. Chociaż teraz jestem bardzo ciekawy, dlaczego urllib nie działa ... – Mac

0

trzeba dodać Content-Disposition nagłówek, czymś tak (chociaż użyłem mod-python tutaj, ale Zasada powinna być taka sama):

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

curl nie potrzebuje, to dlaczego nie? Pyton – Mac

+0

Myślę, że curl robi to po cichu, chociaż nie postawiłbym na to farmy - twoją praktyczną opcją jest użycie Wireshark i po prostu zobaczenie, co działa na łączu między curl a serwerem (nie jest to łatwe w użyciu na Wireshark na localhostu, musielibyśmy mieć do tego oddzielną maszynę). – mrkafk

+0

Najwyraźniej nie: http://pastie.org/private/w4qrctt9t8rx2dzkrg319g#5,15 – Mac

0

Można użyć unirest, zapewnia łatwą metodę ofertę kupna. `

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

`

Możesz sprawdzić kompletny przykład w http://stackandqueue.com/?p=57