|
1 #!/usr/bin/env python |
|
2 # |
|
3 # Copyright 2007 Google Inc. |
|
4 # |
|
5 # Licensed under the Apache License, Version 2.0 (the "License"); |
|
6 # you may not use this file except in compliance with the License. |
|
7 # You may obtain a copy of the License at |
|
8 # |
|
9 # http://www.apache.org/licenses/LICENSE-2.0 |
|
10 # |
|
11 # Unless required by applicable law or agreed to in writing, software |
|
12 # distributed under the License is distributed on an "AS IS" BASIS, |
|
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
14 # See the License for the specific language governing permissions and |
|
15 # limitations under the License. |
|
16 # |
|
17 """Copyright 2008 Python Software Foundation, Ian Bicking, and Google.""" |
|
18 |
|
19 import mimetools |
|
20 import StringIO |
|
21 import sys |
|
22 |
|
23 |
|
24 CONTINUE = 100 |
|
25 SWITCHING_PROTOCOLS = 101 |
|
26 PROCESSING = 102 |
|
27 OK = 200 |
|
28 CREATED = 201 |
|
29 ACCEPTED = 202 |
|
30 NON_AUTHORITATIVE_INFORMATION = 203 |
|
31 NO_CONTENT = 204 |
|
32 RESET_CONTENT = 205 |
|
33 PARTIAL_CONTENT = 206 |
|
34 MULTI_STATUS = 207 |
|
35 IM_USED = 226 |
|
36 MULTIPLE_CHOICES = 300 |
|
37 MOVED_PERMANENTLY = 301 |
|
38 FOUND = 302 |
|
39 SEE_OTHER = 303 |
|
40 NOT_MODIFIED = 304 |
|
41 USE_PROXY = 305 |
|
42 TEMPORARY_REDIRECT = 307 |
|
43 BAD_REQUEST = 400 |
|
44 UNAUTHORIZED = 401 |
|
45 PAYMENT_REQUIRED = 402 |
|
46 FORBIDDEN = 403 |
|
47 NOT_FOUND = 404 |
|
48 METHOD_NOT_ALLOWED = 405 |
|
49 NOT_ACCEPTABLE = 406 |
|
50 PROXY_AUTHENTICATION_REQUIRED = 407 |
|
51 REQUEST_TIMEOUT = 408 |
|
52 CONFLICT = 409 |
|
53 GONE = 410 |
|
54 LENGTH_REQUIRED = 411 |
|
55 PRECONDITION_FAILED = 412 |
|
56 REQUEST_ENTITY_TOO_LARGE = 413 |
|
57 REQUEST_URI_TOO_LONG = 414 |
|
58 UNSUPPORTED_MEDIA_TYPE = 415 |
|
59 REQUESTED_RANGE_NOT_SATISFIABLE = 416 |
|
60 EXPECTATION_FAILED = 417 |
|
61 UNPROCESSABLE_ENTITY = 422 |
|
62 LOCKED = 423 |
|
63 FAILED_DEPENDENCY = 424 |
|
64 UPGRADE_REQUIRED = 426 |
|
65 INTERNAL_SERVER_ERROR = 500 |
|
66 NOT_IMPLEMENTED = 501 |
|
67 BAD_GATEWAY = 502 |
|
68 SERVICE_UNAVAILABLE = 503 |
|
69 GATEWAY_TIMEOUT = 504 |
|
70 HTTP_VERSION_NOT_SUPPORTED = 505 |
|
71 INSUFFICIENT_STORAGE = 507 |
|
72 NOT_EXTENDED = 510 |
|
73 |
|
74 responses = { |
|
75 100: 'Continue', |
|
76 101: 'Switching Protocols', |
|
77 |
|
78 200: 'OK', |
|
79 201: 'Created', |
|
80 202: 'Accepted', |
|
81 203: 'Non-Authoritative Information', |
|
82 204: 'No Content', |
|
83 205: 'Reset Content', |
|
84 206: 'Partial Content', |
|
85 |
|
86 300: 'Multiple Choices', |
|
87 301: 'Moved Permanently', |
|
88 302: 'Found', |
|
89 303: 'See Other', |
|
90 304: 'Not Modified', |
|
91 305: 'Use Proxy', |
|
92 306: '(Unused)', |
|
93 307: 'Temporary Redirect', |
|
94 |
|
95 400: 'Bad Request', |
|
96 401: 'Unauthorized', |
|
97 402: 'Payment Required', |
|
98 403: 'Forbidden', |
|
99 404: 'Not Found', |
|
100 405: 'Method Not Allowed', |
|
101 406: 'Not Acceptable', |
|
102 407: 'Proxy Authentication Required', |
|
103 408: 'Request Timeout', |
|
104 409: 'Conflict', |
|
105 410: 'Gone', |
|
106 411: 'Length Required', |
|
107 412: 'Precondition Failed', |
|
108 413: 'Request Entity Too Large', |
|
109 414: 'Request-URI Too Long', |
|
110 415: 'Unsupported Media Type', |
|
111 416: 'Requested Range Not Satisfiable', |
|
112 417: 'Expectation Failed', |
|
113 |
|
114 500: 'Internal Server Error', |
|
115 501: 'Not Implemented', |
|
116 502: 'Bad Gateway', |
|
117 503: 'Service Unavailable', |
|
118 504: 'Gateway Timeout', |
|
119 505: 'HTTP Version Not Supported', |
|
120 } |
|
121 |
|
122 HTTP_PORT = 80 |
|
123 HTTPS_PORT = 443 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 class HTTPConnection: |
|
130 |
|
131 |
|
132 protocol = 'http' |
|
133 default_port = HTTP_PORT |
|
134 _allow_truncated = True |
|
135 _follow_redirects = False |
|
136 |
|
137 def __init__(self, host, port=None, strict=False, timeout=None): |
|
138 from google.appengine.api import urlfetch |
|
139 self._fetch = urlfetch.fetch |
|
140 self._method_map = { |
|
141 'GET': urlfetch.GET, |
|
142 'POST': urlfetch.POST, |
|
143 'HEAD': urlfetch.HEAD, |
|
144 'PUT': urlfetch.PUT, |
|
145 'DELETE': urlfetch.DELETE, |
|
146 } |
|
147 self.host = host |
|
148 self.port = port |
|
149 self._method = self._url = None |
|
150 self._body = '' |
|
151 self.headers = [] |
|
152 |
|
153 def connect(self): |
|
154 pass |
|
155 |
|
156 def request(self, method, url, body=None, headers=None): |
|
157 self._method = method |
|
158 self._url = url |
|
159 try: |
|
160 self._body = body.read() |
|
161 except AttributeError: |
|
162 self._body = body |
|
163 if headers is None: |
|
164 headers = [] |
|
165 elif hasattr(headers, 'items'): |
|
166 headers = headers.items() |
|
167 self.headers = headers |
|
168 |
|
169 def putrequest(self, request, selector, skip_host=False, skip_accept_encoding=False): |
|
170 self._method = request |
|
171 self._url = selector |
|
172 |
|
173 def putheader(self, header, *lines): |
|
174 line = '\r\n\t'.join(lines) |
|
175 self.headers.append((header, line)) |
|
176 |
|
177 def endheaders(self): |
|
178 pass |
|
179 |
|
180 def set_debuglevel(self, level=None): |
|
181 pass |
|
182 |
|
183 def send(self, data): |
|
184 self._body += data |
|
185 |
|
186 def getresponse(self): |
|
187 if self.port and self.port != self.default_port: |
|
188 host = '%s:%s' % (self.host, self.port) |
|
189 else: |
|
190 host = self.host |
|
191 url = '%s://%s%s' % (self.protocol, host, self._url) |
|
192 headers = dict(self.headers) |
|
193 |
|
194 try: |
|
195 method = self._method_map[self._method.upper()] |
|
196 except KeyError: |
|
197 raise ValueError("%r is an unrecognized HTTP method" % self._method) |
|
198 |
|
199 response = self._fetch(url, self._body, method, headers, |
|
200 self._allow_truncated, self._follow_redirects) |
|
201 return HTTPResponse(response) |
|
202 |
|
203 def close(self): |
|
204 pass |
|
205 |
|
206 |
|
207 class HTTPSConnection(HTTPConnection): |
|
208 |
|
209 protocol = 'https' |
|
210 default_port = HTTPS_PORT |
|
211 |
|
212 def __init__(self, host, port=None, key_file=None, cert_file=None, |
|
213 strict=False, timeout=None): |
|
214 if key_file is not None or cert_file is not None: |
|
215 raise NotImplementedError( |
|
216 "key_file and cert_file arguments are not implemented") |
|
217 HTTPConnection.__init__(self, host, port=port, strict=strict, |
|
218 timeout=timeout) |
|
219 |
|
220 |
|
221 class HTTPResponse(object): |
|
222 |
|
223 def __init__(self, fetch_response): |
|
224 self._fetch_response = fetch_response |
|
225 self.fp = StringIO.StringIO(fetch_response.content) |
|
226 |
|
227 def __getattr__(self, attr): |
|
228 return getattr(self.fp, attr) |
|
229 |
|
230 def getheader(self, name, default=None): |
|
231 return self._fetch_response.headers.get(name, default) |
|
232 |
|
233 def getheaders(self): |
|
234 return self._fetch_response.headers.items() |
|
235 |
|
236 @property |
|
237 def msg(self): |
|
238 msg = mimetools.Message(StringIO.StringIO('')) |
|
239 for name, value in self._fetch_response.headers.items(): |
|
240 msg[name] = value |
|
241 return msg |
|
242 |
|
243 version = 11 |
|
244 |
|
245 @property |
|
246 def status(self): |
|
247 return self._fetch_response.status_code |
|
248 |
|
249 @property |
|
250 def reason(self): |
|
251 return responses.get(self._fetch_response.status_code, 'Unknown') |
|
252 |
|
253 |
|
254 class HTTP: |
|
255 "Compatibility class with httplib.py from 1.5." |
|
256 |
|
257 _http_vsn = 11 |
|
258 _http_vsn_str = 'HTTP/1.1' |
|
259 |
|
260 debuglevel = 0 |
|
261 |
|
262 _connection_class = HTTPConnection |
|
263 |
|
264 def __init__(self, host='', port=None, strict=None): |
|
265 "Provide a default host, since the superclass requires one." |
|
266 |
|
267 if port == 0: |
|
268 port = None |
|
269 |
|
270 self._setup(self._connection_class(host, port, strict)) |
|
271 |
|
272 def _setup(self, conn): |
|
273 self._conn = conn |
|
274 |
|
275 self.send = conn.send |
|
276 self.putrequest = conn.putrequest |
|
277 self.endheaders = conn.endheaders |
|
278 self.set_debuglevel = conn.set_debuglevel |
|
279 |
|
280 conn._http_vsn = self._http_vsn |
|
281 conn._http_vsn_str = self._http_vsn_str |
|
282 |
|
283 self.file = None |
|
284 |
|
285 def connect(self, host=None, port=None): |
|
286 "Accept arguments to set the host/port, since the superclass doesn't." |
|
287 self.__init__(host, port) |
|
288 |
|
289 def getfile(self): |
|
290 "Provide a getfile, since the superclass' does not use this concept." |
|
291 return self.file |
|
292 |
|
293 def putheader(self, header, *values): |
|
294 "The superclass allows only one value argument." |
|
295 self._conn.putheader(header, '\r\n\t'.join(values)) |
|
296 |
|
297 def getreply(self): |
|
298 """Compat definition since superclass does not define it. |
|
299 |
|
300 Returns a tuple consisting of: |
|
301 - server status code (e.g. '200' if all goes well) |
|
302 - server "reason" corresponding to status code |
|
303 - any RFC822 headers in the response from the server |
|
304 """ |
|
305 response = self._conn.getresponse() |
|
306 |
|
307 self.headers = response.msg |
|
308 self.file = response.fp |
|
309 return response.status, response.reason, response.msg |
|
310 |
|
311 def close(self): |
|
312 self._conn.close() |
|
313 |
|
314 self.file = None |
|
315 |
|
316 |
|
317 class HTTPS(HTTP): |
|
318 """Compatibility with 1.5 httplib interface |
|
319 |
|
320 Python 1.5.2 did not have an HTTPS class, but it defined an |
|
321 interface for sending http requests that is also useful for |
|
322 https. |
|
323 """ |
|
324 |
|
325 _connection_class = HTTPSConnection |
|
326 |
|
327 def __init__(self, host='', port=None, key_file=None, cert_file=None, |
|
328 strict=None): |
|
329 if key_file is not None or cert_file is not None: |
|
330 raise NotImplementedError( |
|
331 "key_file and cert_file arguments are not implemented") |
|
332 |
|
333 |
|
334 if port == 0: |
|
335 port = None |
|
336 self._setup(self._connection_class(host, port, key_file, |
|
337 cert_file, strict)) |
|
338 |
|
339 self.key_file = key_file |
|
340 self.cert_file = cert_file |
|
341 |
|
342 |
|
343 class HTTPException(Exception): |
|
344 pass |
|
345 |
|
346 class NotConnected(HTTPException): |
|
347 pass |
|
348 |
|
349 class InvalidURL(HTTPException): |
|
350 pass |
|
351 |
|
352 class UnknownProtocol(HTTPException): |
|
353 def __init__(self, version): |
|
354 self.version = version |
|
355 HTTPException.__init__(self, version) |
|
356 |
|
357 class UnknownTransferEncoding(HTTPException): |
|
358 pass |
|
359 |
|
360 class UnimplementedFileMode(HTTPException): |
|
361 pass |
|
362 |
|
363 class IncompleteRead(HTTPException): |
|
364 def __init__(self, partial): |
|
365 self.partial = partial |
|
366 HTTPException.__init__(self, partial) |
|
367 |
|
368 class ImproperConnectionState(HTTPException): |
|
369 pass |
|
370 |
|
371 class CannotSendRequest(ImproperConnectionState): |
|
372 pass |
|
373 |
|
374 class CannotSendHeader(ImproperConnectionState): |
|
375 pass |
|
376 |
|
377 class ResponseNotReady(ImproperConnectionState): |
|
378 pass |
|
379 |
|
380 class BadStatusLine(HTTPException): |
|
381 def __init__(self, line): |
|
382 self.line = line |
|
383 HTTPException.__init__(self, line) |
|
384 |
|
385 error = HTTPException |