~mcfletch/+junk/restclient

« back to all changes in this revision

Viewing changes to encodemultipart.py

  • Committer: Mike C. Fletcher
  • Date: 2008-12-15 04:24:39 UTC
  • Revision ID: mcfletch@vrplumber.com-20081215042439-oftazes45b3d9m7z
Initial creation of a rest client for turbogears applications

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/local/bin/python
 
2
# 2005/08/04
 
3
#v1.0.1 
 
4
 
 
5
# upload_test.py
 
6
# A set of functions to test file uploading using urllib2.
 
7
 
 
8
# Copyright Michael Foord, 2004 & 2005.
 
9
# Released subject to the BSD License
 
10
# Please see http://www.voidspace.org.uk/documents/BSD-LICENSE.txt
 
11
 
 
12
# For information about bugfixes, updates and support, please join the
 
13
# Pythonutils mailing list.
 
14
# http://voidspace.org.uk/mailman/listinfo/pythonutils_voidspace.org.uk
 
15
# Comments, suggestions and bug reports welcome.
 
16
# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
 
17
# E-mail fuzzyman@voidspace.org.uk
 
18
 
 
19
########################################################################
 
20
 
 
21
"""
 
22
A function to encode files *and* normal fields into a multipart/form-data body for urllib2 to use.
 
23
This allows multiple file uploads via urllib2.
 
24
 
 
25
Based on http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306
 
26
With inspiration from urllib2_file.py by Fabien Seisen, http://fabien.seisen.org/python/ (without his twiddly bits)
 
27
 
 
28
It actually uses my upload script located at http://www.voidspace.xennos.com
 
29
It has a maximum upload size - so you shouldn't be able to overflow my webspace !!
 
30
"""
 
31
 
 
32
import urllib2
 
33
import mimetypes
 
34
import mimetools
 
35
 
 
36
def encode_multipart_formdata(fields, files, BOUNDARY = '-----'+mimetools.choose_boundary()+'-----'):
 
37
    """ Encodes fields and files for uploading.
 
38
    fields is a sequence of (name, value) elements for regular form fields - or a dictionary.
 
39
    files is a sequence of (name, filename, value) elements for data to be uploaded as files.
 
40
    Return (content_type, body) ready for urllib2.Request instance
 
41
    You can optionally pass in a boundary string to use or we'll let mimetools provide one.
 
42
    """    
 
43
    CRLF = '\r\n'
 
44
    L = []
 
45
    if isinstance(fields, dict):
 
46
        fields = fields.items()
 
47
    for (key, value) in fields:
 
48
        L.append('--' + BOUNDARY)
 
49
        L.append('Content-Disposition: form-data; name="%s"' % key)
 
50
        L.append('')
 
51
        L.append(value)
 
52
    for (key, filename, value) in files:
 
53
        filetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
 
54
        L.append('--' + BOUNDARY)
 
55
        L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
 
56
#        if not filetype.startswith('text'):
 
57
#            L.append('Content-Transfer-Encoding: binary')
 
58
        L.append('Content-Type: %s' % filetype)
 
59
#        L.append('Content-Length: %s\r\n' % str(len(value)))
 
60
        L.append('')
 
61
        L.append(value)
 
62
    L.append('--' + BOUNDARY + '--')
 
63
    L.append('')
 
64
    body = CRLF.join(L)
 
65
    content_type = 'multipart/form-data; boundary=%s' % BOUNDARY        # XXX what if no files are encoded
 
66
    return content_type, body
 
67
 
 
68
def build_request(theurl, fields, files, txheaders=None):
 
69
    """Given the fields to set and the files to encode it returns a fully formed urllib2.Request object.
 
70
    You can optionally pass in additional headers to encode into the opject. (Content-type and Content-length will be overridden if they are set).
 
71
    fields is a sequence of (name, value) elements for regular form fields - or a dictionary.
 
72
    files is a sequence of (name, filename, value) elements for data to be uploaded as files.    
 
73
    """
 
74
    content_type, body = encode_multipart_formdata(fields, files)
 
75
    if not txheaders: txheaders = {}
 
76
    txheaders['Content-type'] = content_type
 
77
    txheaders['Content-length'] = str(len(body))
 
78
    return urllib2.Request(theurl, body, txheaders)
 
79
 
 
80
########################################################################
 
81
# The test below assumes you have a script 'upload.py' located at 'theurl'
 
82
# It should be a CGI taking file.1, file.2 and file.3
 
83
# and uploading them into the 'uploadDir' which is relative to 'theurl'
 
84
# upload.py from http://www.voidspace.org.uk/atlantibots/recipebook.html should do this...
 
85
 
 
86
if __name__ == '__main__':
 
87
    theurl = 'http://www.voidspace.xennos.com/cgi-bin/upload.py'
 
88
    uploadDir = '../upload/'                                       # must end in '/' for urljoin to work
 
89
    fields = {}
 
90
    file1 = ('file.1', 'file1.txt', 'test'*100)
 
91
    file2 = ('file.2', 'file2.txt', 'PADDING'*100)
 
92
    file3 = ('file.3', 'file3.dat', ''.join([chr(num) for num in range(100)]))
 
93
    files = (file1, file2, file3)
 
94
 
 
95
    print urllib2.urlopen(build_request(theurl, fields, files)).read()
 
96
 
 
97
    from urlparse import urljoin
 
98
    uploadDirpath = urljoin(theurl, uploadDir)
 
99
    file1path = urljoin(uploadDirpath, file1[1])
 
100
    file2path = urljoin(uploadDirpath, file2[1])
 
101
    file3path = urljoin(uploadDirpath, file3[1])
 
102
 
 
103
    print
 
104
    print "Testing 'file1' == uploaded file : "
 
105
    filehandle = urllib2.urlopen(file1path)
 
106
    print filehandle.read() == file1[2]
 
107
    print
 
108
    print "Testing 'file2' == uploaded file : "
 
109
    filehandle = urllib2.urlopen(file2path)
 
110
    print filehandle.read() == file2[2]
 
111
    print
 
112
    print "Testing 'file3' == uploaded file : "
 
113
    filehandle = urllib2.urlopen(file3path)
 
114
    print filehandle.read() == file3[2]
 
115
    
 
116
 
 
117
    
 
118
 
 
119
"""
 
120
TODO/ISSUES
 
121
Will incorrectly set content-type as multipart/form-data even if no files are sent.
 
122
This should work but isn't the best way to do it.
 
123
Will this work with binary data or does it need base64 encoding ? - shouldn't do application/octet-stream can include binary. It doesn't work with Xitmai as localhost though, fails on chr(26) ??
 
124
 
 
125
CHANGELOG
 
126
Version 1.0.1       2005/08/04
 
127
Added theurl parameter to build_request.
 
128
    (Fix by Andrew Dalke)
 
129
 
 
130
Version 1.0.0       02-10-04
 
131
Works fine with the Linux host at www.xennos.com and my upload.py
 
132
"""