1
import httplib, urlparse, uuid, os
5
def mimeencode(boundary, key, value):
6
key = email.utils.quote(key)
9
return """--%(boundary)s
10
Content-Disposition: form-data; name="%(key)s"
11
Content-Length: %(size)i
16
class FileProxy(object):
17
def __init__(self, filename=None, file_obj=None,
18
size=None, contentType=None):
20
if filename is None and file_obj is None:
21
raise ValueError("At least one of filename and file_obj must be set")
24
self.contentType = contentType
26
self.contentType = mimetypes.guess_type(filename)[0]
28
self.contentType = 'application/octet-stream'
31
self.filename = filename
34
self.file_obj = file(filename)
36
self.file_obj = file_obj
39
self.size = os.path.getsize(filename)
45
self.filename = "fileobj"
47
self.filename = filename
48
self.file_obj = file_obj
50
self.data = file_obj.read()
51
self.size = len(self.data)
55
def getMimeHeader(self, name, boundary):
56
name = email.utils.quote(name)
57
filename = email.utils.quote(self.filename)
58
contentType = self.contentType
60
return ("""--%(boundary)s
61
Content-Disposition: form-data; name="%(name)s"; filename="%(filename)s"
62
Content-Type: %(contentType)s
63
Content-Length: %(size)i
65
""" % vars()).replace("\n", "\r\n")
67
def getLen(self, name, boundary):
68
return len(self.getMimeHeader(name, boundary)) + self.size
70
class HTTPClient(object):
72
Wrapper around httplib to simplify some operations
74
def __init__(self, url):
75
if not url.startswith("http"):
77
self.proto, hostport, self.baseurl = urlparse.urlsplit(url)[:3]
78
if self.proto not in ("http", "https"):
79
raise ValueError("Invalid protocol")
82
self.host, self.port = hostport.split(":", 1)
83
self.port = int(self.port)
86
if self.proto == "http":
87
self.port = httplib.HTTP_PORT
88
elif self.proto == "https":
89
self.port = httplib.HTTPS_PORT
91
def _getConnection(self):
92
if self.proto == "http":
93
return httplib.HTTPConnection(self.host, self.port, True)
95
return httplib.HTTPSConnection(self.host, self.port, strict=True)
97
def get(self, url, body=None, headers={}):
99
Requests self.baseurl / url from the server
101
Returns an httplib.HTTPResponse object which supports
102
a .read() method to get the response data.
104
url = urlparse.urljoin(self.baseurl, url)
105
conn = self._getConnection()
106
conn.request("GET", url, body, headers)
107
return conn.getresponse()
109
def post(self, url, params={}, headers={}, multipart=True):
111
Sends a POST request to the given URL
112
Params is a dictionary of key / value pairs
114
If value can be an instance of FileProxy to handle file uploads
117
raise NotImplementedError("Non-multipart POSTs are not yet supported")
119
boundary = uuid.uuid4().hex
120
headers['Content-Type'] = 'multipart/form-data; boundary=%s' % boundary
121
url = urlparse.urljoin(self.baseurl, url)
125
for key, value in params.items():
126
if isinstance(value, FileProxy):
127
fileParts.append((key, value))
129
normalParts.append(mimeencode(boundary, key, value))
131
normalParts = "\n".join(normalParts).replace("\n", "\r\n")
133
# Figure out how big the entire body will be
134
totalSize = len(normalParts)
136
footer = "\r\n--%(boundary)s--\r\n" % vars()
137
totalSize += len(footer)
139
for name,f in fileParts:
140
totalSize += f.getLen(name, boundary)
142
headers['Content-Length'] = totalSize
144
conn = self._getConnection()
145
conn.putrequest("POST", url)
146
for k, v in headers.items():
149
conn.send(normalParts)
150
for name, f in fileParts:
151
data = f.getMimeHeader(name, boundary)
157
data = f.file_obj.read(4096)
162
return conn.getresponse()
164
def postFileObj(self, url, filename, fileparam, type=None, filesize=None,
165
fileobj=None, params={}, headers={}):
167
POSTs a file to the given url
169
params is a dictionary of other parameters to include in the POST
172
boundary = uuid.uuid4().hex
174
headers['Content-Type'] = 'multipart/form-data; boundary=%s' % boundary
175
url = urlparse.urljoin(self.baseurl, url)
178
fileobj = open(filename)
181
filesize = os.path.getsize(filename)
184
type = "application/octet-stream"
187
for k,v in params.items():
188
name = email.utils.quote(k)
191
mimePieces.append("""\
193
Content-Disposition: form-data; name="%(name)s"
194
Content-Length: %(size)i
199
name = email.utils.quote(fileparam)
200
basename = os.path.basename(filename)
201
contentType = type or "application/octet-stream"
202
mimePieces.append("""\
204
Content-Disposition: form-data; name="%(name)s"; filename=%(basename)s
205
Content-Type: %(contentType)s
206
Content-Length: %(filesize)i
210
mimeHeaders = "\n".join(mimePieces).replace("\n", "\r\n")
211
totalSize = len(mimeHeaders) + filesize + len(boundary) + 4
212
headers['Content-Length'] = totalSize
214
conn = self._getConnection()
215
conn.putrequest("POST", url)
216
for k,v in headers.items():
219
conn.send(mimeHeaders)
222
data = fileobj.read(4096)
227
conn.send("\r\n--%(boundary)s" % vars())
228
return conn.getresponse()