webentwicklung-frage-antwort-db.com.de

Python POST Binärdaten

Ich schreibe einen Code für die Schnittstelle mit Redmine und muss im Rahmen des Vorgangs einige Dateien hochladen, bin mir aber nicht sicher, wie ich eine POST request from python Enthält eine Binärdatei.

Ich versuche die Befehle nachzuahmen hier :

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

In python (siehe unten), aber es scheint nicht zu funktionieren. Ich bin nicht sicher, ob das Problem mit der Kodierung der Datei zusammenhängt oder ob etwas mit den Headern nicht stimmt.

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

Ich habe Zugriff auf den Server und es sieht aus wie ein Codierungsfehler:

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

Grundsätzlich ist das, was Sie tun, richtig. Betrachtet man die von Ihnen verlinkten RedMine-Dokumente, so scheint das Suffix nach dem Punkt in der URL den Typ der veröffentlichten Daten (.json für JSON, .xml für XML) anzugeben, was mit der Antwort übereinstimmt, die Sie erhalten - Processing by AttachmentsController#upload as XML. Ich vermute, es gibt einen Fehler in der Dokumentation. Um Binärdaten zu veröffentlichen, sollten Sie versuchen, http://redmine/uploads Anstelle von http://redmine/uploads.xml Zu verwenden.

Übrigens empfehle ich sehr gut und sehr beliebt Requests Bibliothek für http in Python. Es ist viel besser als das, was in der Standardbibliothek (urllib2) steht. Es unterstützt auch Authentifizierung, aber ich habe es hier aus Gründen der Kürze übersprungen.

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

AKTUALISIEREN

Um herauszufinden, warum dies mit Requests, aber nicht mit urllib2 funktioniert, müssen wir den Unterschied in dem, was gesendet wird, untersuchen. Um dies zu sehen, sende ich Datenverkehr an einen http-Proxy (Fiddler), der auf Port 8888 ausgeführt wird:

Anfragen verwenden

import requests

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

wir sehen

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

und mit 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)

wir bekommen

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

Ich sehe keine Unterschiede, die ein anderes Verhalten rechtfertigen würden. Allerdings ist es nicht ungewöhnlich, dass http-Server den User-Agent - Header überprüfen und das Verhalten basierend auf seinem Wert variieren. Versuchen Sie, die von Requests gesendeten Header nacheinander zu ändern, sodass sie denen entsprechen, die von urllib2 gesendet werden, und prüfen Sie, ob sie nicht mehr funktionieren.

49
Piotr Dobrogost

Dies hat nichts mit einem fehlerhaften Upload zu tun. Der HTTP-Fehler gibt 401 eindeutig als nicht autorisiert an und gibt an, dass das CSRF-Token ungültig ist. Versuchen Sie, mit dem Upload ein gültiges CSRF-Token zu senden.

Weitere Informationen zu CSRF-Tokens finden Sie hier:

Was ist ein CSRF-Token? Was ist seine Bedeutung und wie funktioniert es?

2
Josh Liptzin

Sie können nirest verwenden. Dies bietet eine einfache Methode zum Posten von Anfragen. `

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

`

Das vollständige Beispiel finden Sie unter http://stackandqueue.com/?p=57

0
gvir

sie müssen den Content-Disposition-Header wie folgt hinzufügen (obwohl ich hier Mod-Python verwendet habe, aber das Prinzip sollte dasselbe sein):

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