Package shttp :: Module sHTTP
[hide private]
[frames] | no frames]

Source Code for Module shttp.sHTTP

  1  #coding: iso-8859-1; 
  2  ############################################################################ 
  3  #                                                                          # 
  4  #    This file 'shttp/sHTTP.py'                                            # 
  5  #    is part of 'SPyRO: Simple Python Remote Objects'                      # 
  6  #    Copyright (C) 2004-2005 by Eric Sadit Téllez Avila                    # 
  7  #    sadit@lsc.fie.umich.mx or donsadit@gmail.com                          # 
  8  #                                                                          # 
  9  #    This program is free software; you can redistribute it and#or modify  # 
 10  #    it under the terms of the GNU General Public License as published by  # 
 11  #    the Free Software Foundation; either version 2 of the License, or     # 
 12  #    (at your option) any later version.                                   # 
 13  #                                                                          # 
 14  #    This program is distributed in the hope that it will be useful,       # 
 15  #    but WITHOUT ANY WARRANTY; without even the implied warranty of        # 
 16  #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         # 
 17  #    GNU General Public License for more details.                          # 
 18  #                                                                          # 
 19  #    You should have received a copy of the GNU General Public License     # 
 20  #    along with this program; if not, write to the                         # 
 21  #    Free Software Foundation, Inc.,                                       # 
 22  #    59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             # 
 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  #HTTPHead=sre.compile(r"^([\S]+):\s*([^\r]+)\s*\n$") 
 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  # responses hash taked from BaseHTTPServer: 
 83  # Table mapping response codes to messages; entries have the 
 84  # form {code: (shortmessage, longmessage)}. 
 85  # See http://www.w3.org/hypertext/WWW/Protocols/HTTP/HTRESP.html 
 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   
153 -class HTTPStopServer(Exception): pass
154 -class HTTPError(Exception): pass
155 -class HTTPUnexpectedStatus(HTTPError): pass
156 -class HTTPBadRequest(HTTPError): pass
157 -class HTTPNullRequest(HTTPBadRequest): pass
158 -class HTTPBadResponse(HTTPError): pass
159 -class HTTPBadHeader(HTTPError): pass
160
161 -class HTTPFileNotFound(HTTPError): pass
162 -class HTTPResolverError(HTTPError): pass
163
164 -class HTTPURIHandler(HTTPError): pass
165
166 -class HTTPBase:
167 """ Common code to HTTP client and HTTP server """
168 - def close(self):
169 """ close the Object """ 170 self.__closed = True 171 self.close_socket()
172
173 - def close_socket(self):
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
186 - def __del__(self):
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 # self.input = self.output = self.fp = commsocket.makefile("w+b",2048) 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
213 - def __getitem__(self, v):
214 """ Get the |v| HTTP header """ 215 return self.headers[v]
216
217 - def _read(self):
218 #warn.debug("================================ _READ ") 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 # Trash 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 #self.fp.readline(512) 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 # data = "" 258 # if length == -1: 259 # _data = None 260 # while True: 261 # warn.debug("READ") 262 # _data = self.fp.read(1) 263 # warn.debug("READED") 264 # if _data == "": break 265 # print "_____________>'%s'"%(_data) 266 # data = data + self.fp.read(1) 267 # else: 268 # data = self.fp.read(length) 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
282 - def request_error(self, string):
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
288 - def header_error(self, string):
289 """ Called when a bad header is present""" 290 raise HTTPBadHeader, "HTTP Header Error '%s'"%(string)
291
292 - def _readline(self,max=512):
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 # while True: 310 # try: 311 # return self.fp.readline(max) 312 # except WantReadError, error: 313 # continue 314
315 - def read_headers(self):
316 """ Read the headers and the (response status|request)""" 317 self.headers.clear() 318 line = self._readline() 319 #line = self.fp.readline(512) 320 # warn.debug( "HEAD:",repr(line) ) 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 # warn.debug( "Line: ", repr(line) ) 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
340 - def _write(self, data):
341 while True: 342 try: 343 self.fp.write(data) 344 except WantWriteError: 345 continue 346 break
347
348 - def put_headers(self, headers = None):
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 # warn.debug( "<HEADERS: |%s|>"%("".join(strings) )) 362
363 - def get_method(self):
364 """ What method is used to communicate (i.e. GET, POST, etc)""" 365 return self.request[0]
366
367 - def get_uri(self):
368 """ The URI or requested location """ 369 return self.request[1]
370
371 - def get_httpversion(self):
372 """ The version of the HTTP used""" 373 return self.request[2]
374
375 -class HTTP(HTTPBase):
376 """ The light weight HTTP Server """
377 - def __del__(self):
378 HTTPBase.__del__(self)
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 #self.headers = {'Set-Cookie': []} 388 self.handlers = handlers 389 self.set_servername('sHTTP server (sHTTP) v0.2')
390 391 # def set_socket(self, commsocket, usingssl=False): 392 # warn.stack("\n\n\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx set_socket in server ") 393 # HTTPBase.set_socket(self, commsocket, usingssl) 394
395 - def set_servername(self, servername):
396 self.servername = servername
397
398 - def send_response_header(self, httpversion = HTTPVersion, status = 200, 399 status_string = None):
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 # check if already sent response's header 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 # print ("%s %s %s\r\n"%(httpversion,status,status_string)) 413
414 - def init_request(self):
415 self._done_recv_headers = False 416 self._done_recv_response = True # no used yet 417 self._done_send_headers = False 418 self._done_send_response = False
419
420 - def send_headers(self, headers, status = 200):
421 self.send_response_header() 422 headers['Server'] = self.servername 423 # check for headers sent already 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 # print "xxxxxxxx SEND PAGE>", self.get_httpversion(), self.headers 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 # print "xxxxxxxx>", self.get_httpversion(), self.headers 451 if self.get_httpversion() != 'HTTP/1.1': 452 if self.headers.get('Connection','').lower() != 'keep-alive': 453 self.close_socket() 454 # print "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>" 455 raise HTTPStopServer("Clean stopped, connection close")
456
457 - def resolve(self):
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
469 - def request_error(self,string):
470 """ Request error manager """ 471 self.send_page(responses[400][1],status=400) 472 raise HTTPBadRequest, "Request Error '%s'"%(string)
473
474 - def header_error(self,string):
475 """ Header error manager """ 476 self.send_page(responses[400][0],status=400) 477 raise HTTPBadHeader, "Header Error '%s'"%(string)
478
479 -class HTTPClient(HTTPBase):
480 """ The HTTP Client """
481 - def set_socket(self, conn=None, usingssl=False):
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
491 - def __del__(self):
492 HTTPBase.__del__(self)
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
505 - def request_error(self, string):
506 """ Manage the request error as bas response """ 507 raise HTTPBadResponse, "HTTP Response Error %s '%s'"%(self.socket.getpeername(),repr(string))
508
509 - def send_request(self, uri, msg='', version=HTTPVersion, method='POST', 510 headers=None):
511 """ Send a request to the server """ 512 if headers is None: headers = {} 513 try: 514 if self.socket is None: self.set_socket(None) 515 self.__send_request(uri, msg, version, method, headers) 516 except socket.error, errdata: 517 self.set_socket(None) 518 self.__send_request(uri, msg, version, method, headers)
519
520 - def __send_request(self, uri, msg, version, method, headers):
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 #warn.debug("xxxxxxxxxxxxxxxxx>%s %s %s\r\n"%(method, uri, version)) 528 #warn.stack() 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
540 - def get_response_status(self):
541 return int(self.request[1])
542
543 - def get_response_reason(self):
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