61

redmine とやり取りするコードを書いていて、プロセスの一部としていくつかのファイルをアップロードする必要がありますが、バイナリ ファイルを含む python から POST リクエストを行う方法がわかりません。

ここでコマンドを模倣しようとしています:

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

python(以下)では、動作していないようです。問題がファイルのエンコードに何らかの形で関連しているのか、それともヘッダーに何か問題があるのか​​ はわかりません。

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

サーバーにアクセスできますが、エンコード エラーのようです。

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

4 に答える 4

78

基本的にあなたがすることは正しいです。リンクしたredmineドキュメントを見ると、URLのドットの後のサフィックスは、投稿されたデータのタイプ(JSONの場合は.json、XMLの場合は.xml)を示しているようです。これは、取得した応答と一致します- Processing by AttachmentsController#upload as XML。ドキュメントにバグがあるのではないかと思います。バイナリデータを投稿するhttp://redmine/uploadsには、の代わりにurlを使用してみてくださいhttp://redmine/uploads.xml

ところで、 Pythonのhttp用の非常に優れた非常に人気のあるRequestsライブラリを強くお勧めします。標準のlib(urllib2)にあるものよりもはるかに優れています。認証もサポートしていますが、ここでは簡潔にするためにスキップしました。

import requests
with open('./x.png', 'rb') as f:
    data = f.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

アップデート

これがRequestsで機能するが、urllib2では機能しない理由を見つけるには、送信される内容の違いを調べる必要があります。これを確認するために、ポート8888で実行されているhttpプロキシ(Fiddler)にトラフィックを送信しています。

リクエストの使用

import requests

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

私たちは見る

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

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)

我々が得る

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

あなたが観察する異なる行動を正当化するような違いは見当たりません。User-Agenthttpサーバーがヘッダーを検査し、その値に基づいて動作を変更することは珍しいことではないと言っています。Requestsによって送信されるヘッダーを1つずつ変更して、urllib2によって送信されるヘッダーと同じになるようにして、動作が停止するタイミングを確認してください。

于 2013-01-21T23:17:58.853 に答える
3

これは、不正な形式のアップロードとは関係ありません。HTTP エラーは 401 の無許可を明確に示しており、CSRF トークンが無効であることを示しています。アップロードで有効な CSRF トークンを送信してみてください。

csrf トークンの詳細はこちら:

CSRFトークンとは?その重要性とその仕組みは?

于 2014-10-17T19:44:46.063 に答える
2

Content-Disposition ヘッダーを次のように追加する必要があります (ただし、ここでは mod-python を使用しましたが、原則は同じである必要があります)。

request.headers_out['Content-Disposition'] = 'attachment; filename=%s' % myfname
于 2013-01-16T18:38:00.207 に答える
-1

unirestを使用できます。リクエストを投稿する簡単な方法を提供します。`

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()
于 2016-01-23T10:19:17.637 に答える