~chris-atlee/blobs/main

« back to all changes in this revision

Viewing changes to blobs/client/httpclient.py

  • Committer: Chris AtLee
  • Date: 2008-02-19 16:55:03 UTC
  • Revision ID: chris@atlee.ca-20080219165503-0i5wcwtx04zphrhd
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import httplib, urlparse, uuid, os
 
2
import mimetypes
 
3
import email.utils
 
4
 
 
5
def mimeencode(boundary, key, value):
 
6
    key = email.utils.quote(key)
 
7
    value = str(value)
 
8
    size = len(value)
 
9
    return """--%(boundary)s
 
10
Content-Disposition: form-data; name="%(key)s"
 
11
Content-Length: %(size)i
 
12
 
 
13
%(value)s
 
14
""" % vars()
 
15
 
 
16
class FileProxy(object):
 
17
    def __init__(self, filename=None, file_obj=None,
 
18
            size=None, contentType=None):
 
19
 
 
20
        if filename is None and file_obj is None:
 
21
            raise ValueError("At least one of filename and file_obj must be set")
 
22
 
 
23
        if contentType:
 
24
            self.contentType = contentType
 
25
        elif filename:
 
26
            self.contentType = mimetypes.guess_type(filename)[0]
 
27
        else:
 
28
            self.contentType = 'application/octet-stream'
 
29
 
 
30
        if filename:
 
31
            self.filename = filename
 
32
            self.data = None
 
33
            if not file_obj:
 
34
                self.file_obj = file(filename)
 
35
            else:
 
36
                self.file_obj = file_obj
 
37
 
 
38
            if not size:
 
39
                self.size = os.path.getsize(filename)
 
40
            else:
 
41
                self.size = size
 
42
 
 
43
        elif file_obj:
 
44
            if not filename:
 
45
                self.filename = "fileobj"
 
46
            else:
 
47
                self.filename = filename
 
48
            self.file_obj = file_obj
 
49
            if not size:
 
50
                self.data = file_obj.read()
 
51
                self.size = len(self.data)
 
52
            else:
 
53
                self.size = size
 
54
 
 
55
    def getMimeHeader(self, name, boundary):
 
56
        name = email.utils.quote(name)
 
57
        filename = email.utils.quote(self.filename)
 
58
        contentType = self.contentType
 
59
        size = self.size
 
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
 
64
 
 
65
""" % vars()).replace("\n", "\r\n")
 
66
 
 
67
    def getLen(self, name, boundary):
 
68
        return len(self.getMimeHeader(name, boundary)) + self.size
 
69
 
 
70
class HTTPClient(object):
 
71
    """
 
72
    Wrapper around httplib to simplify some operations
 
73
    """
 
74
    def __init__(self, url):
 
75
        if not url.startswith("http"):
 
76
            url = "http://" + url
 
77
        self.proto, hostport, self.baseurl  = urlparse.urlsplit(url)[:3]
 
78
        if self.proto not in ("http", "https"):
 
79
            raise ValueError("Invalid protocol")
 
80
 
 
81
        if ":" in hostport:
 
82
            self.host, self.port = hostport.split(":", 1)
 
83
            self.port = int(self.port)
 
84
        else:
 
85
            self.host = hostport
 
86
            if self.proto == "http":
 
87
                self.port = httplib.HTTP_PORT
 
88
            elif self.proto == "https":
 
89
                self.port = httplib.HTTPS_PORT
 
90
 
 
91
    def _getConnection(self):
 
92
        if self.proto == "http":
 
93
            return httplib.HTTPConnection(self.host, self.port, True)
 
94
        else:
 
95
            return httplib.HTTPSConnection(self.host, self.port, strict=True)
 
96
 
 
97
    def get(self, url, body=None, headers={}):
 
98
        """
 
99
        Requests self.baseurl / url from the server
 
100
 
 
101
        Returns an httplib.HTTPResponse object which supports
 
102
        a .read() method to get the response data.
 
103
        """
 
104
        url = urlparse.urljoin(self.baseurl, url)
 
105
        conn = self._getConnection()
 
106
        conn.request("GET", url, body, headers)
 
107
        return conn.getresponse()
 
108
 
 
109
    def post(self, url, params={}, headers={}, multipart=True):
 
110
        """
 
111
        Sends a POST request to the given URL
 
112
        Params is a dictionary of key / value pairs
 
113
 
 
114
        If value can be an instance of FileProxy to handle file uploads
 
115
        """
 
116
        if not multipart:
 
117
            raise NotImplementedError("Non-multipart POSTs are not yet supported")
 
118
 
 
119
        boundary = uuid.uuid4().hex
 
120
        headers['Content-Type'] = 'multipart/form-data; boundary=%s' % boundary
 
121
        url = urlparse.urljoin(self.baseurl, url)
 
122
 
 
123
        normalParts = []
 
124
        fileParts = []
 
125
        for key, value in params.items():
 
126
            if isinstance(value, FileProxy):
 
127
                fileParts.append((key, value))
 
128
            else:
 
129
                normalParts.append(mimeencode(boundary, key, value))
 
130
 
 
131
        normalParts = "\n".join(normalParts).replace("\n", "\r\n")
 
132
 
 
133
        # Figure out how big the entire body will be
 
134
        totalSize = len(normalParts)
 
135
        # Add final boundary
 
136
        footer = "\r\n--%(boundary)s--\r\n" % vars()
 
137
        totalSize += len(footer)
 
138
 
 
139
        for name,f in fileParts:
 
140
            totalSize += f.getLen(name, boundary)
 
141
 
 
142
        headers['Content-Length'] = totalSize
 
143
 
 
144
        conn = self._getConnection()
 
145
        conn.putrequest("POST", url)
 
146
        for k, v in headers.items():
 
147
            conn.putheader(k, v)
 
148
        conn.endheaders()
 
149
        conn.send(normalParts)
 
150
        for name, f in fileParts:
 
151
            data = f.getMimeHeader(name, boundary)
 
152
            conn.send(data)
 
153
            if f.data:
 
154
                conn.send(f.data)
 
155
            else:
 
156
                while True:
 
157
                    data = f.file_obj.read(4096)
 
158
                    if not data:
 
159
                        break
 
160
                    conn.send(data)
 
161
        conn.send(footer)
 
162
        return conn.getresponse()
 
163
 
 
164
    def postFileObj(self, url, filename, fileparam, type=None, filesize=None,
 
165
            fileobj=None, params={}, headers={}):
 
166
        """
 
167
        POSTs a file to the given url
 
168
 
 
169
        params is a dictionary of other parameters to include in the POST
 
170
        body
 
171
        """
 
172
        boundary = uuid.uuid4().hex
 
173
 
 
174
        headers['Content-Type'] = 'multipart/form-data; boundary=%s' % boundary
 
175
        url = urlparse.urljoin(self.baseurl, url)
 
176
 
 
177
        if fileobj is None:
 
178
            fileobj = open(filename)
 
179
 
 
180
        if filesize is None:
 
181
            filesize = os.path.getsize(filename)
 
182
 
 
183
        if type is None:
 
184
            type = "application/octet-stream"
 
185
 
 
186
        mimePieces = []
 
187
        for k,v in params.items():
 
188
            name = email.utils.quote(k)
 
189
            value = str(v)
 
190
            size = len(value)
 
191
            mimePieces.append("""\
 
192
--%(boundary)s
 
193
Content-Disposition: form-data; name="%(name)s"
 
194
Content-Length: %(size)i
 
195
 
 
196
%(value)s
 
197
""" % vars())
 
198
 
 
199
        name = email.utils.quote(fileparam)
 
200
        basename = os.path.basename(filename)
 
201
        contentType = type or "application/octet-stream"
 
202
        mimePieces.append("""\
 
203
--%(boundary)s
 
204
Content-Disposition: form-data; name="%(name)s"; filename=%(basename)s
 
205
Content-Type: %(contentType)s
 
206
Content-Length: %(filesize)i
 
207
 
 
208
""" % vars())
 
209
 
 
210
        mimeHeaders = "\n".join(mimePieces).replace("\n", "\r\n")
 
211
        totalSize = len(mimeHeaders) + filesize + len(boundary) + 4
 
212
        headers['Content-Length'] = totalSize
 
213
 
 
214
        conn = self._getConnection()
 
215
        conn.putrequest("POST", url)
 
216
        for k,v in headers.items():
 
217
            conn.putheader(k, v)
 
218
        conn.endheaders()
 
219
        conn.send(mimeHeaders)
 
220
 
 
221
        while True:
 
222
            data = fileobj.read(4096)
 
223
            if not data:
 
224
                break
 
225
            conn.send(data)
 
226
 
 
227
        conn.send("\r\n--%(boundary)s" % vars())
 
228
        return conn.getresponse()