2009-03-25 30 views
44

Vấn đề: Khi đăng dữ liệu với urllib2 của Python, tất cả dữ liệu được mã hóa và gửi dưới dạng Content-Type: application/x-www-form-urlencoded. Khi tải lên tệp, thay vào đó, Content-Type sẽ được đặt thành multipart/form-data và nội dung được mã hóa MIME. Một cuộc thảo luận về vấn đề này là ở đây: http://code.activestate.com/recipes/146306/Sử dụng MultipartPostHandler để POST dạng dữ liệu với Python

Để khắc phục hạn chế này một số lập trình mạnh tạo ra một thư viện gọi MultipartPostHandler mà tạo ra một OpenerDirector bạn có thể sử dụng với urllib2 để chủ yếu là tự động POST với multipart/form-data. Bản sao của thư viện này có tại đây: http://peerit.blogspot.com/2007/07/multipartposthandler-doesnt-work-for.html

Tôi mới sử dụng Python và không thể làm cho thư viện này hoạt động. Tôi đã viết về cơ bản đoạn mã sau. Khi tôi chụp nó trong một proxy HTTP cục bộ, tôi có thể thấy rằng dữ liệu vẫn được mã hóa URL và không mã hóa MIME nhiều phần. Xin hãy giúp tôi tìm ra những gì tôi đang làm sai hoặc một cách tốt hơn để hoàn thành công việc này. Cảm ơn :-)

FROM_ADDR = '[email protected]' 

try: 
    data = open(file, 'rb').read() 
except: 
    print "Error: could not open file %s for reading" % file 
    print "Check permissions on the file or folder it resides in" 
    sys.exit(1) 

# Build the POST request 
url = "http://somedomain.com/?action=analyze"  
post_data = {} 
post_data['analysisType'] = 'file' 
post_data['executable'] = data 
post_data['notification'] = 'email' 
post_data['email'] = FROM_ADDR 

# MIME encode the POST payload 
opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler) 
urllib2.install_opener(opener) 
request = urllib2.Request(url, post_data) 
request.set_proxy('127.0.0.1:8080', 'http') # For testing with Burp Proxy 

# Make the request and capture the response 
try: 
    response = urllib2.urlopen(request) 
    print response.geturl() 
except urllib2.URLError, e: 
    print "File upload failed..." 

EDIT1: Cảm ơn phản hồi của bạn. Tôi nhận thức được giải pháp ActiveState httplib này (tôi liên kết với nó ở trên). Tôi muốn trừu tượng đi vấn đề và sử dụng một số lượng tối thiểu của mã để tiếp tục sử dụng urllib2 như thế nào tôi đã được. Bất kỳ ý tưởng tại sao opener không được cài đặt và sử dụng?

Trả lời

57

Dường như cách dễ nhất và tương thích nhất để có được xung quanh vấn đề này là sử dụng 'tấm áp phích' module.

# test_client.py 
from poster.encode import multipart_encode 
from poster.streaminghttp import register_openers 
import urllib2 

# Register the streaming http handlers with urllib2 
register_openers() 

# Start the multipart/form-data encoding of the file "DSC0001.jpg" 
# "image1" is the name of the parameter, which is normally set 
# via the "name" parameter of the HTML <input> tag. 

# headers contains the necessary Content-Type and Content-Length 
# datagen is a generator object that yields the encoded parameters 
datagen, headers = multipart_encode({"image1": open("DSC0001.jpg")}) 

# Create the Request object 
request = urllib2.Request("http://localhost:5000/upload_image", datagen, headers) 
# Actually do the request, and get the response 
print urllib2.urlopen(request).read() 

Điều này làm việc hoàn hảo và tôi không phải muck với httplib. Module này có sẵn ở đây: http://atlee.ca/software/poster/index.html

+1

Đây chính xác là những gì tôi cần! Thanh danh. –

+1

Tôi biết đây là một bài cũ, nhưng tôi nhận được điều này từ poster: 'AttributeError: multipart_yielder instance không có thuộc tính '__len __'' tự hỏi nếu có ai khác có prob này. – Andy

+5

@nalroff Bạn đã không gọi 'poster.streaminghttp.register_openers()' –

32

Tìm thấy công thức này để gửi nhiều phần dữ liệu sử dụng httplib trực tiếp (không thư viện bên ngoài tham gia)

import httplib 
import mimetypes 

def post_multipart(host, selector, fields, files): 
    content_type, body = encode_multipart_formdata(fields, files) 
    h = httplib.HTTP(host) 
    h.putrequest('POST', selector) 
    h.putheader('content-type', content_type) 
    h.putheader('content-length', str(len(body))) 
    h.endheaders() 
    h.send(body) 
    errcode, errmsg, headers = h.getreply() 
    return h.file.read() 

def encode_multipart_formdata(fields, files): 
    LIMIT = '----------lImIt_of_THE_fIle_eW_$' 
    CRLF = '\r\n' 
    L = [] 
    for (key, value) in fields: 
     L.append('--' + LIMIT) 
     L.append('Content-Disposition: form-data; name="%s"' % key) 
     L.append('') 
     L.append(value) 
    for (key, filename, value) in files: 
     L.append('--' + LIMIT) 
     L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)) 
     L.append('Content-Type: %s' % get_content_type(filename)) 
     L.append('') 
     L.append(value) 
    L.append('--' + LIMIT + '--') 
    L.append('') 
    body = CRLF.join(L) 
    content_type = 'multipart/form-data; boundary=%s' % LIMIT 
    return content_type, body 

def get_content_type(filename): 
    return mimetypes.guess_type(filename)[0] or 'application/octet-stream' 
+6

Điều này có vẻ đúng, nhưng các module tấm áp phích là đúng hơn. – bentford

+3

Tôi nghĩ phương pháp này là "độc lập" hơn vì nó không yêu cầu mô-đun mới và có thể được "biên dịch" với py2exe để chạy trên windows, dưới dạng tệp * .exe. Thêm mô-đun 'áp phích' cũng được chấp nhận nhưng không hoạt động. Phương pháp này là tốt nhất cho tôi. Hoan hô cho tác giả! – garmoncheg

+1

Tệp "giá trị" có cần phải được mã hóa bằng cách nào đó hoặc chỉ là dòng tối ưu thuần túy không? –

29

Chỉ cần sử dụng python-requests, nó sẽ thiết lập tiêu đề thích hợp và đừng upload cho bạn:

import requests 
files = {"form_input_field_name": open("filename", "rb")} 
requests.post("http://httpbin.org/post", files=files) 
+1

Cảm ơn, hoạt động như một sự quyến rũ! – bszom

+0

+1 tuyệt vời, ngắn gọn và chính xác những gì tôi cần! – Bogatyr

+0

Chỉ định tên của trường nhập html là rất quan trọng! Cảm ơn –

0

tôi những gì một trùng, 2 năm, 6 tháng trước, tôi tạo ra dự án

https://pypi.python.org/pypi/MultipartPostHandler2, sửa chữa MultipartPostHandler cho hệ thống utf-8. Tôi cũng đã thực hiện một số cải tiến nhỏ, bạn được hoan nghênh để kiểm tra nó :)

+0

Hey, bro! Tôi nghĩ rằng tên gói được chọn của bạn làm cho tôi không kiểm tra nó. – pylover

+0

xin lỗi không hiểu, tôi không thể sửa đổi MultipartPostHandler, vì vậy tôi phải gọi nó là MultipartPostHandler2 –

+0

pypi hỗ trợ nhiều phiên bản cho mỗi gói, nếu tên này đã được dùng, bạn nên chọn một tên gói tốt khác. Pypi là của chúng tôi. Chúng tôi đều chịu trách nhiệm với những gì chúng tôi làm với nó – pylover

1

Tôi đã gặp phải vấn đề tương tự và tôi cần thực hiện một biểu mẫu nhiều phần mà không cần sử dụng thư viện bên ngoài. Tôi đã viết toàn bộ blogpost about the issues I ran into.

Tôi đã sử dụng phiên bản sửa đổi http://code.activestate.com/recipes/146306/. Mã trong url đó thực sự chỉ nối thêm nội dung của tệp dưới dạng chuỗi, điều này có thể gây ra sự cố với tệp nhị phân. Đây là mã làm việc của tôi.

import mimetools 
import mimetypes 
import io 
import http 
import json 


form = MultiPartForm() 
form.add_field("form_field", "my awesome data") 

# Add a fake file  
form.add_file(key, os.path.basename(filepath), 
    fileHandle=codecs.open("/path/to/my/file.zip", "rb")) 

# Build the request 
url = "http://www.example.com/endpoint" 
schema, netloc, url, params, query, fragments = urlparse.urlparse(url) 

try: 
    form_buffer = form.get_binary().getvalue() 
    http = httplib.HTTPConnection(netloc) 
    http.connect() 
    http.putrequest("POST", url) 
    http.putheader('Content-type',form.get_content_type()) 
    http.putheader('Content-length', str(len(form_buffer))) 
    http.endheaders() 
    http.send(form_buffer) 
except socket.error, e: 
    raise SystemExit(1) 

r = http.getresponse() 
if r.status == 200: 
    return json.loads(r.read()) 
else: 
    print('Upload failed (%s): %s' % (r.status, r.reason)) 

class MultiPartForm(object): 
    """Accumulate the data to be used when posting a form.""" 

    def __init__(self): 
     self.form_fields = [] 
     self.files = [] 
     self.boundary = mimetools.choose_boundary() 
     return 

    def get_content_type(self): 
     return 'multipart/form-data; boundary=%s' % self.boundary 

    def add_field(self, name, value): 
     """Add a simple field to the form data.""" 
     self.form_fields.append((name, value)) 
     return 

    def add_file(self, fieldname, filename, fileHandle, mimetype=None): 
     """Add a file to be uploaded.""" 
     body = fileHandle.read() 
     if mimetype is None: 
      mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' 
     self.files.append((fieldname, filename, mimetype, body)) 
     return 

    def get_binary(self): 
     """Return a binary buffer containing the form data, including attached files.""" 
     part_boundary = '--' + self.boundary 

     binary = io.BytesIO() 
     needsCLRF = False 
     # Add the form fields 
     for name, value in self.form_fields: 
      if needsCLRF: 
       binary.write('\r\n') 
      needsCLRF = True 

      block = [part_boundary, 
       'Content-Disposition: form-data; name="%s"' % name, 
       '', 
       value 
      ] 
      binary.write('\r\n'.join(block)) 

     # Add the files to upload 
     for field_name, filename, content_type, body in self.files: 
      if needsCLRF: 
       binary.write('\r\n') 
      needsCLRF = True 

      block = [part_boundary, 
       str('Content-Disposition: file; name="%s"; filename="%s"' % \ 
       (field_name, filename)), 
       'Content-Type: %s' % content_type, 
       '' 
       ] 
      binary.write('\r\n'.join(block)) 
      binary.write('\r\n') 
      binary.write(body) 


     # add closing boundary marker, 
     binary.write('\r\n--' + self.boundary + '--\r\n') 
     return binary 
0

Để trả lời câu hỏi của OP về lý do mã ban đầu không hoạt động, trình xử lý đã chuyển không phải là phiên bản của lớp. Dòng

# MIME encode the POST payload 
opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler) 

nên đọc

opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler()) 
Các vấn đề liên quan