1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import sre
26 import os
27 import sys
28 import warn
29
30 import socket, SocketServer
31 WantReadError = None
32 WantWriteError = None
33 try:
34 import OpenSSL
35 import OpenSSL.SSL
36 SSL = OpenSSL.SSL
37 WantReadError = SSL.WantReadError
38 WantWriteError = SSL.WantWriteError
39 except ImportError, err:
40 warn.debug("Unable to use HTTPS as transport layer ", err)
41 WantReadError = OSError
42 WantWriteError = OSError
43 SSL = None
44
45 from threading import Lock
46 from SocketServer import ThreadingTCPServer
47 import Queue
48 import time
49 warn = warn.Warn("shttp.sHTTP")
50 import gzip
51 from cStringIO import StringIO
52
53 try:
54 socket_SHUT_RDWR = socket.SHUT_RDWR
55 except:
56 socket_SHUT_RDWR = 2
57
58 try:
59 True
60 except:
61 __builtins__['True'] = 1
62 __builtins__['False'] = 0
63
64 try:
65 from select import select
66 _USE_SELECT = True
67 except:
68 _USE_SELECT = False
69
70 __doc__ = """
71 HTTP package provides a Server and Client (minimal) implementations of the HTTP
72 protocol. Both, server and client, can convert any socket into server or client
73 respectively. The server obtain handlers to methods (GET, POST, etc) and
74 help on most commonly used functions """
75
76 HTTPNewline = "\r\n"
77
78 HTTPHead=sre.compile(r"^([\S]+):\s*([^\r]+)\s*$")
79 HTTPRequest=sre.compile(r"^(\S+)\s+(\S+)\s+([^\r\n]+)")
80 HTTPVersion="HTTP/1.1"
81 HTTPBlankline=sre.compile(r"^\s*$")
82
83
84
85
86 responses = {
87 100: ('Continue', 'Request received, please continue'),
88 101: ('Switching Protocols',
89 'Switching to new protocol; obey Upgrade header'),
90
91 200: ('OK', 'Request fulfilled, document follows'),
92 201: ('Created', 'Document created, URL follows'),
93 202: ('Accepted',
94 'Request accepted, processing continues off-line'),
95 203: ('Non-Authoritative Information', 'Request fulfilled from cache'),
96 204: ('No response', 'Request fulfilled, nothing follows'),
97 205: ('Reset Content', 'Clear input form for further input.'),
98 206: ('Partial Content', 'Partial content follows.'),
99
100 300: ('Multiple Choices',
101 'Object has several resources -- see URI list'),
102 301: ('Moved Permanently', 'Object moved permanently -- see URI list'), 302: ('Found', 'Object moved temporarily -- see URI list'),
103 303: ('See Other', 'Object moved -- see Method and URL list'),
104 304: ('Not modified',
105 'Document has not changed since given time'),
106 305: ('Use Proxy',
107 'You must use proxy specified in Location to access this '
108 'resource.'),
109 307: ('Temporary Redirect',
110 'Object moved temporarily -- see URI list'),
111
112 400: ('Bad request',
113 'Bad request syntax or unsupported method'),
114
115 401: ('Unauthorized',
116 'No permission -- see authorization schemes'),
117 402: ('Payment required',
118 'No payment -- see charging schemes'),
119 403: ('Forbidden',
120 'Request forbidden -- authorization will not help'),
121 404: ('Not Found', 'Nothing matches the given URI'),
122 405: ('Method Not Allowed',
123 'Specified method is invalid for this server.'),
124 406: ('Not Acceptable', 'URI not available in preferred format.'),
125 407: ('Proxy Authentication Required', 'You must authenticate with '
126 'this proxy before proceeding.'),
127 408: ('Request Time-out', 'Request timed out; try again later.'),
128 409: ('Conflict', 'Request conflict.'),
129 410: ('Gone',
130 'URI no longer exists and has been permanently removed.'),
131 411: ('Length Required', 'Client must specify Content-Length.'),
132 412: ('Precondition Failed', 'Precondition in headers is false.'),
133 413: ('Request Entity Too Large', 'Entity is too large.'),
134 414: ('Request-URI Too Long', 'URI is too long.'),
135 415: ('Unsupported Media Type', 'Entity body in unsupported format.'),
136 416: ('Requested Range Not Satisfiable',
137 'Cannot satisfy request range.'),
138 417: ('Expectation Failed',
139 'Expect condition could not be satisfied.'),
140 500: ('Internal error', 'Server got itself in trouble'),
141 501: ('Not Implemented',
142 'Server does not support this operation'),
143 502: ('Bad Gateway', 'Invalid responses from another server/proxy.'),
144 503: ('Service temporarily overloaded',
145 'The server cannot process the request due to a high load'),
146 504: ('Gateway timeout',
147 'The gateway server did not receive a timely response'),
148 505: ('HTTP Version not supported', 'Cannot fulfill request.'),
149 }
150
151 HTTPMessage="%s %d %s\r\nContent-Type: text/plain\r\nContent-Length: 0\r\n"
152
160
163
165
167 """ Common code to HTTP client and HTTP server """
169 """ close the Object """
170 self.__closed = True
171 self.close_socket()
172
174 if self.socket is None: return
175 try:
176 self.fp.close()
177 if self.usingssl:
178 self.socket.shutdown()
179 self.socket.sock_shutdown(socket_SHUT_RDWR)
180 else:
181 self.socket.shutdown(socket_SHUT_RDWR)
182 self.socket.close()
183 except Exception, err:
184 pass
185
187 if not self.__closed: self.close()
188
189 - def set_socket(self, commsocket, usingssl=False):
190 """ Change the socket that connecs peers """
191 if not (self.socket is None): self.close_socket()
192 self.usingssl = usingssl
193 self.socket = commsocket
194 self.input = self.output = self.fp = socket._fileobject(commsocket, "w+b",2048)
195
196
197 - def __init__(self, commsocket, usingssl=False):
198 """ Initialize the HTTPBase instance. |commsocket| is the socket
199 that connects peers.
200
201 NOTE:
202
203 Every specialized sub-class must be careful with flushes over
204 self.fp and its file objects. The flushes have an important impact in
205 performance of applications.
206 """
207 self.socket = None
208 self.set_socket(commsocket, usingssl)
209 self.headers = {}
210 self.request = None
211 self.__closed = None
212
214 """ Get the |v| HTTP header """
215 return self.headers[v]
216
218
219 length = int(self.headers.get('Content-Length',-1))
220 traenc = self.headers.get("Transfer-Encoding")
221 if traenc and traenc == "chunked":
222 data = ''
223 while True:
224 line = self._readline()
225 chunkedoptions = sre.split("\s*;\s*", line)
226 length = int(chunkedoptions[0],16)
227 if length == 0: break
228 while True:
229 try:
230 data = self.fp.read(length)
231 except WantReadError, err:
232 continue
233 break
234 self._readline()
235
236 if _USE_SELECT:
237 rlist,wlist,xlist = [self.input.fileno()], [], []
238 while _USE_SELECT:
239 r = select(rlist, wlist, xlist, 0.5)
240 r = r[0]
241 if len(r) == 0: break
242
243 while True:
244 try:
245 self.fp.read(1)
246 except WantReadError:
247 continue
248 break
249 else:
250 data = ''
251 while True:
252 try:
253 data = self.fp.read(length)
254 except WantReadError, err:
255 continue
256 break
257
258
259
260
261
262
263
264
265
266
267
268
269 return data
270
271 - def get_content(self,just_file_pointer=False, get_raw_data=False):
272 """ Return the content of the document (a POST in the server,
273 a response int the client) """
274 if get_raw_data: return self._read()
275 if self.headers.get('Content-Encoding','').find('gzip') == -1:
276 if just_file_pointer: return self.fp
277 return self._read()
278 g = gzip.GzipFile(filename=None, mode="rb", fileobj = StringIO(self._read()))
279 if just_file_pointer: return g
280 return g.read()
281
283 """ Called when an HTTP Request error ocurrs, in the server
284 is called when a response error is present. Raises the
285 respective exception in both modes"""
286 raise HTTPBadRequest, "HTTP Request Error %s '%s'"%(self.socket.getpeername(),string)
287
289 """ Called when a bad header is present"""
290 raise HTTPBadHeader, "HTTP Header Error '%s'"%(string)
291
293 line = ''
294 while len(line) < max:
295 try:
296 line = line + self.fp.read(1)
297 except WantReadError,err:
298 continue
299 try:
300 if line[-1] == '\n':
301 if line[-2:] == '\r\n':
302 line = line[:-2]
303 else:
304 line = line[:-1]
305 break
306 except IndexError:
307 pass
308 return line
309
310
311
312
313
314
316 """ Read the headers and the (response status|request)"""
317 self.headers.clear()
318 line = self._readline()
319
320
321 resp = HTTPRequest.match(line)
322 if not resp:
323 if line == '': raise HTTPNullRequest("")
324 self.request_error(line)
325 self.request = resp.group(1,2,3)
326 while True:
327 line=self._readline()
328
329 if HTTPBlankline.match(line): break
330 m=HTTPHead.match(line)
331 if not m:
332 self.header_error(line)
333 header, value = m.group(1,2)
334 if header == 'Cookie' or header == 'Accept':
335 h = self.headers.get(header, [])
336 h.append(value)
337 value = h
338 self.headers[header] = value
339
341 while True:
342 try:
343 self.fp.write(data)
344 except WantWriteError:
345 continue
346 break
347
349 """ Send the headers, including the response|request """
350 if headers is None: headers = {}
351 if not headers.has_key('Connection'):
352 headers['Connection']='Keep-Alive'
353 strings = [ ]
354 for k,v in headers.items():
355 if k == 'Cookie' or k == 'Accept':
356 for values in v: strings.append("%s: %s\r\n"%(k,values))
357 else:
358 strings.append("%s: %s\r\n"%(k,v))
359 strings.append("\r\n")
360 self._write("".join(strings))
361
362
364 """ What method is used to communicate (i.e. GET, POST, etc)"""
365 return self.request[0]
366
368 """ The URI or requested location """
369 return self.request[1]
370
372 """ The version of the HTTP used"""
373 return self.request[2]
374
375 -class HTTP(HTTPBase):
376 """ The light weight HTTP Server """
379
380 - def __init__(self, reqsocket, handlers=None):
381 """ Initialize the HTTP server, uses the socket |reqsocket| and
382 uses |handlers| (a dictionary methodname -> handlerfunction) as
383 handlers to methods. An URI handler can be used inside the method
384 handler.
385 """
386 HTTPBase.__init__(self, reqsocket)
387
388 self.handlers = handlers
389 self.set_servername('sHTTP server (sHTTP) v0.2')
390
391
392
393
394
396 self.servername = servername
397
400 """ Send the first line with the response of the request
401 |httpversion| is the version of the http response
402 |status| the reponse status
403 |status_string| a human readable status """
404
405 if self._done_send_response:
406 return
407 else:
408 self._done_send_response = True
409 if not status_string:
410 status_string = responses[status][0]
411 self._write("%s %s %s\r\n"%(httpversion,status,status_string))
412
413
415 self._done_recv_headers = False
416 self._done_recv_response = True
417 self._done_send_headers = False
418 self._done_send_response = False
419
421 self.send_response_header()
422 headers['Server'] = self.servername
423
424 if self._done_send_headers:
425 return
426 else:
427 self._done_send_headers = True
428 self.put_headers(headers)
429
430 - def send_page(self, stringdata, headers=None, status=200):
431 """ Send a page with data |stringdata|, headers |headers|, and
432 the status |status| (default OK: 200)"""
433
434 if headers == None: headers = {}
435 if not headers.has_key('Content-Type'):
436 headers['Content-Type'] = 'text/plain'
437 x = self.headers.get('Accept-Encoding')
438 if x and x.find('gzip') != -1:
439 headers['Content-Encoding'] = 'x-gzip'
440 string = StringIO()
441 gzfile = gzip.GzipFile(filename=None, mode="wb", fileobj = string)
442 gzfile.write(stringdata)
443 gzfile.flush()
444 gzfile.close()
445 stringdata = string.getvalue()
446 headers['Content-Length'] = len(stringdata)
447 self.send_headers(headers,status)
448 self._write(stringdata)
449 self.fp.flush()
450
451 if self.get_httpversion() != 'HTTP/1.1':
452 if self.headers.get('Connection','').lower() != 'keep-alive':
453 self.close_socket()
454
455 raise HTTPStopServer("Clean stopped, connection close")
456
458 """ Resolve a request using handlers, if there is no handler,
459 it responses with 405 error"""
460 self.init_request()
461 self.read_headers()
462 h=self.handlers.get(self.get_method(), None)
463 if h:
464 h(self)
465 else:
466 self.send_page(responses[405][1],status=405)
467 return
468
473
478
480 """ The HTTP Client """
482 """ Change the socket, to manage connetion errors and https connections """
483 if self.socket: self.close_socket()
484 conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
485 if usingssl:
486 context = SSL.Context(SSL.SSLv23_METHOD)
487 conn = SSL.Connection(context, conn)
488 conn.connect(self.address)
489 HTTPBase.set_socket(self, conn, usingssl)
490
493
494 - def __init__(self, address, defaultheaders=None, sockettimeout=None, usingssl=False):
495 """ Initialize the HTTP Client, it uses the |address| to connect """
496 self.address = address
497 HTTPBase.__init__(self, None, usingssl)
498 if defaultheaders is None: defaultheaders = {}
499 self.defaultheaders = defaultheaders
500 try:
501 self.socket.settimeout(sockettimeout)
502 except Exception, err:
503 warn.debug("There is no socket settimeout: ", err)
504
506 """ Manage the request error as bas response """
507 raise HTTPBadResponse, "HTTP Response Error %s '%s'"%(self.socket.getpeername(),repr(string))
508
519
521 """ The real send_request, don't use if you do not know what are you
522 doing. This is used only by persistant connections """
523 if method == 'POST': headers['Content-Length'] = len(msg)
524 for k,v in self.defaultheaders.items():
525 if not headers.has_key(k): headers[k] = v
526 self._write("%s %s %s\r\n"%(method, uri, version))
527
528
529 self.put_headers(headers)
530 if method == 'POST':
531 self._write(msg)
532 self.fp.flush()
533 self.read_headers()
534 if method == 'HEAD' and self.headers.get('Connection') == 'close':
535 self.close_socket()
536 if self.request[1][0] != '2':
537 raise HTTPUnexpectedStatus,'Unexpected Status %s: %s'%(self.get_response_status(),
538 self.get_response_reason())
539
541 return int(self.request[1])
542
544 return self.request[2].rstrip()
545
546 - def get_content(self, just_file_pointer=False, get_raw_data=False):
547 r = HTTPBase.get_content(self,just_file_pointer, get_raw_data)
548 if get_raw_data: return r
549 length = int(self.headers.get('Content-Length',-1))
550 traenc = self.headers.get("Transfer-Encoding","")
551 if not just_file_pointer:
552 if self.headers.get("Connection","") == 'close':
553 self.close_socket()
554 elif not traenc and length < 0:
555 self.close_socket()
556 return r
557