天天看點

urllib2實作檔案上傳

需要做什麼?

如果使用get方法向伺服器發送如下資料:

name: zhangsan

from: beijing

content-type為application/x-www-form-urlencoded,則會傳輸資料:

name=zhangsan&from=beijing

可以看到兩組資料被&分割,這樣伺服器就能夠正确解析用戶端上傳的資料。那當使用post方法送出資料時,應該使用什麼樣的分割符呢? boundary

怎麼做?

  1. boundary不能與form表單的其他資料重複。
  2. boundary在整個請求中要保持一緻。

拼接如下表單:

—xxx—

Content-Disposition: form-data; name=”name”

zhangsan

—xxx—

Content-Disposition: form-data; name=”from”

beijing

—xxx—

Content-Disposition: file; name=”record”; filename=”record.txt”

Content-Type: text/plain

file upload blog.

代碼實作

# encoding: utf-8
# client.py
import itertools
import mimetools
import mimetypes
from cStringIO import StringIO 
import urllib
import urllib2

class MultiPartForm():

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

    def add_field(self, name, value):
        """添加field資料到form表單"""
        self.form_fields.append((name, value))

    def add_file(self, fieldname, filename, file_obj, mimetype=None):
        """添加檔案到表單"""
        if not mimetype:
            mimetype = mimetypes.guess_type(filename)[] or 'application/octet-stream'
        self.files.append((fieldname, filename, mimetype, file_obj.read()))

    def __str__(self):
        """拼接form封包"""
        parts = []
        part_boundary = "--%s" % self.boundary

        # 添加fields
        parts.extend(
            [part_boundary,
            'Content-Disposition: form-data; name="%s"' %name,
            '',
            value,] for name, value in self.form_fields
            )       

        # 添加要上傳的files
        parts.extend(
            [part_boundary,
            'Content-Disposition: file; name="%s"; filename="%s"' % (field_name, filename),
            'Content-Type: %s' % content_type,
            '',
            body,] for field_name, filename, content_type, body in self.files
            )

        # 壓平parts添加boundary終止符
        flattened = list(itertools.chain(*parts))
        flattened.append('--%s--' % self.boundary)
        flattened.append('')
        return '\r\n'.join(flattened)

if __name__ == '__main__':
    form = MultiPartForm()
    form.add_field('name', 'zhangsan')
    form.add_field('from', 'beijing')

    form.add_file('record', 'record.txt', file_obj = StringIO('urllib2 file upload.'))

    request = urllib2.Request('http://localhost:8080/')
    body = str(form)
    request.add_header('Content-type', 'multipart/form-data; boundary=%s' % form.boundary)
    request.add_header('Content-length', len(body))
    request.add_data(body)

    print
    print 'Request'
    print request.get_data()

    print 
    print 'Response'
    print urllib2.urlopen(request).read()
           
# server.py
# framework 'web.py'
import web

urls = (
    '/', 'hello'
)
app = web.application(urls, globals())

class hello:        
    def POST(self):
        form = web.input()
        return '\r\n'.join(['%s=%s' %(k,v) for k,v in form.items()])

if __name__ == "__main__":
    app.run()
           
# result
python urllib2_upload_files.py 

Request
--127.0.1.1.1000.31866.1426991278.082.1
Content-Disposition: form-data; name="name"

zhangsan
--127.0.1.1.1000.31866.1426991278.082.1
Content-Disposition: form-data; name="from"

beijing
--127.0.1.1.1000.31866.1426991278.082.1
Content-Disposition: file; name="record"; filename="record.txt"
Content-Type: text/plain

urllib2 file upload.
--127.0.1.1.1000.31866.1426991278.082.1--


Response
record=urllib2 file upload.
from=beijing
name=zhangsan