OpenSecurity/install/web.py-0.37/web/httpserver.py
author om
Mon, 02 Dec 2013 14:02:05 +0100
changeset 3 65432e6c6042
permissions -rwxr-xr-x
initial deployment and project layout commit
     1 __all__ = ["runsimple"]
     2 
     3 import sys, os
     4 from SimpleHTTPServer import SimpleHTTPRequestHandler
     5 import urllib
     6 import posixpath
     7 
     8 import webapi as web
     9 import net
    10 import utils
    11 
    12 def runbasic(func, server_address=("0.0.0.0", 8080)):
    13     """
    14     Runs a simple HTTP server hosting WSGI app `func`. The directory `static/` 
    15     is hosted statically.
    16 
    17     Based on [WsgiServer][ws] from [Colin Stewart][cs].
    18     
    19   [ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html
    20   [cs]: http://www.owlfish.com/
    21     """
    22     # Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/)
    23     # Modified somewhat for simplicity
    24     # Used under the modified BSD license:
    25     # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
    26 
    27     import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse
    28     import socket, errno
    29     import traceback
    30 
    31     class WSGIHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
    32         def run_wsgi_app(self):
    33             protocol, host, path, parameters, query, fragment = \
    34                 urlparse.urlparse('http://dummyhost%s' % self.path)
    35 
    36             # we only use path, query
    37             env = {'wsgi.version': (1, 0)
    38                    ,'wsgi.url_scheme': 'http'
    39                    ,'wsgi.input': self.rfile
    40                    ,'wsgi.errors': sys.stderr
    41                    ,'wsgi.multithread': 1
    42                    ,'wsgi.multiprocess': 0
    43                    ,'wsgi.run_once': 0
    44                    ,'REQUEST_METHOD': self.command
    45                    ,'REQUEST_URI': self.path
    46                    ,'PATH_INFO': path
    47                    ,'QUERY_STRING': query
    48                    ,'CONTENT_TYPE': self.headers.get('Content-Type', '')
    49                    ,'CONTENT_LENGTH': self.headers.get('Content-Length', '')
    50                    ,'REMOTE_ADDR': self.client_address[0]
    51                    ,'SERVER_NAME': self.server.server_address[0]
    52                    ,'SERVER_PORT': str(self.server.server_address[1])
    53                    ,'SERVER_PROTOCOL': self.request_version
    54                    }
    55 
    56             for http_header, http_value in self.headers.items():
    57                 env ['HTTP_%s' % http_header.replace('-', '_').upper()] = \
    58                     http_value
    59 
    60             # Setup the state
    61             self.wsgi_sent_headers = 0
    62             self.wsgi_headers = []
    63 
    64             try:
    65                 # We have there environment, now invoke the application
    66                 result = self.server.app(env, self.wsgi_start_response)
    67                 try:
    68                     try:
    69                         for data in result:
    70                             if data: 
    71                                 self.wsgi_write_data(data)
    72                     finally:
    73                         if hasattr(result, 'close'): 
    74                             result.close()
    75                 except socket.error, socket_err:
    76                     # Catch common network errors and suppress them
    77                     if (socket_err.args[0] in \
    78                        (errno.ECONNABORTED, errno.EPIPE)): 
    79                         return
    80                 except socket.timeout, socket_timeout: 
    81                     return
    82             except:
    83                 print >> web.debug, traceback.format_exc(),
    84 
    85             if (not self.wsgi_sent_headers):
    86                 # We must write out something!
    87                 self.wsgi_write_data(" ")
    88             return
    89 
    90         do_POST = run_wsgi_app
    91         do_PUT = run_wsgi_app
    92         do_DELETE = run_wsgi_app
    93 
    94         def do_GET(self):
    95             if self.path.startswith('/static/'):
    96                 SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
    97             else:
    98                 self.run_wsgi_app()
    99 
   100         def wsgi_start_response(self, response_status, response_headers, 
   101                               exc_info=None):
   102             if (self.wsgi_sent_headers):
   103                 raise Exception \
   104                       ("Headers already sent and start_response called again!")
   105             # Should really take a copy to avoid changes in the application....
   106             self.wsgi_headers = (response_status, response_headers)
   107             return self.wsgi_write_data
   108 
   109         def wsgi_write_data(self, data):
   110             if (not self.wsgi_sent_headers):
   111                 status, headers = self.wsgi_headers
   112                 # Need to send header prior to data
   113                 status_code = status[:status.find(' ')]
   114                 status_msg = status[status.find(' ') + 1:]
   115                 self.send_response(int(status_code), status_msg)
   116                 for header, value in headers:
   117                     self.send_header(header, value)
   118                 self.end_headers()
   119                 self.wsgi_sent_headers = 1
   120             # Send the data
   121             self.wfile.write(data)
   122 
   123     class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
   124         def __init__(self, func, server_address):
   125             BaseHTTPServer.HTTPServer.__init__(self, 
   126                                                server_address, 
   127                                                WSGIHandler)
   128             self.app = func
   129             self.serverShuttingDown = 0
   130 
   131     print "http://%s:%d/" % server_address
   132     WSGIServer(func, server_address).serve_forever()
   133 
   134 # The WSGIServer instance. 
   135 # Made global so that it can be stopped in embedded mode.
   136 server = None
   137 
   138 def runsimple(func, server_address=("0.0.0.0", 8080)):
   139     """
   140     Runs [CherryPy][cp] WSGI server hosting WSGI app `func`. 
   141     The directory `static/` is hosted statically.
   142 
   143     [cp]: http://www.cherrypy.org
   144     """
   145     global server
   146     func = StaticMiddleware(func)
   147     func = LogMiddleware(func)
   148     
   149     server = WSGIServer(server_address, func)
   150 
   151     if server.ssl_adapter:
   152         print "https://%s:%d/" % server_address
   153     else:
   154         print "http://%s:%d/" % server_address
   155 
   156     try:
   157         server.start()
   158     except (KeyboardInterrupt, SystemExit):
   159         server.stop()
   160         server = None
   161 
   162 def WSGIServer(server_address, wsgi_app):
   163     """Creates CherryPy WSGI server listening at `server_address` to serve `wsgi_app`.
   164     This function can be overwritten to customize the webserver or use a different webserver.
   165     """
   166     import wsgiserver
   167     
   168     # Default values of wsgiserver.ssl_adapters uses cherrypy.wsgiserver
   169     # prefix. Overwriting it make it work with web.wsgiserver.
   170     wsgiserver.ssl_adapters = {
   171         'builtin': 'web.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
   172         'pyopenssl': 'web.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
   173     }
   174     
   175     server = wsgiserver.CherryPyWSGIServer(server_address, wsgi_app, server_name="localhost")
   176         
   177     def create_ssl_adapter(cert, key):
   178         # wsgiserver tries to import submodules as cherrypy.wsgiserver.foo.
   179         # That doesn't work as not it is web.wsgiserver. 
   180         # Patching sys.modules temporarily to make it work.
   181         import types
   182         cherrypy = types.ModuleType('cherrypy')
   183         cherrypy.wsgiserver = wsgiserver
   184         sys.modules['cherrypy'] = cherrypy
   185         sys.modules['cherrypy.wsgiserver'] = wsgiserver
   186         
   187         from wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
   188         adapter = pyOpenSSLAdapter(cert, key)
   189         
   190         # We are done with our work. Cleanup the patches.
   191         del sys.modules['cherrypy']
   192         del sys.modules['cherrypy.wsgiserver']
   193 
   194         return adapter
   195 
   196     # SSL backward compatibility
   197     if (server.ssl_adapter is None and
   198         getattr(server, 'ssl_certificate', None) and
   199         getattr(server, 'ssl_private_key', None)):
   200         server.ssl_adapter = create_ssl_adapter(server.ssl_certificate, server.ssl_private_key)
   201 
   202     server.nodelay = not sys.platform.startswith('java') # TCP_NODELAY isn't supported on the JVM
   203     return server
   204 
   205 class StaticApp(SimpleHTTPRequestHandler):
   206     """WSGI application for serving static files."""
   207     def __init__(self, environ, start_response):
   208         self.headers = []
   209         self.environ = environ
   210         self.start_response = start_response
   211 
   212     def send_response(self, status, msg=""):
   213         self.status = str(status) + " " + msg
   214 
   215     def send_header(self, name, value):
   216         self.headers.append((name, value))
   217 
   218     def end_headers(self):
   219         pass
   220 
   221     def log_message(*a): pass
   222 
   223     def __iter__(self):
   224         environ = self.environ
   225 
   226         self.path = environ.get('PATH_INFO', '')
   227         self.client_address = environ.get('REMOTE_ADDR','-'), \
   228                               environ.get('REMOTE_PORT','-')
   229         self.command = environ.get('REQUEST_METHOD', '-')
   230 
   231         from cStringIO import StringIO
   232         self.wfile = StringIO() # for capturing error
   233 
   234         try:
   235             path = self.translate_path(self.path)
   236             etag = '"%s"' % os.path.getmtime(path)
   237             client_etag = environ.get('HTTP_IF_NONE_MATCH')
   238             self.send_header('ETag', etag)
   239             if etag == client_etag:
   240                 self.send_response(304, "Not Modified")
   241                 self.start_response(self.status, self.headers)
   242                 raise StopIteration
   243         except OSError:
   244             pass # Probably a 404
   245 
   246         f = self.send_head()
   247         self.start_response(self.status, self.headers)
   248 
   249         if f:
   250             block_size = 16 * 1024
   251             while True:
   252                 buf = f.read(block_size)
   253                 if not buf:
   254                     break
   255                 yield buf
   256             f.close()
   257         else:
   258             value = self.wfile.getvalue()
   259             yield value
   260 
   261 class StaticMiddleware:
   262     """WSGI middleware for serving static files."""
   263     def __init__(self, app, prefix='/static/'):
   264         self.app = app
   265         self.prefix = prefix
   266         
   267     def __call__(self, environ, start_response):
   268         path = environ.get('PATH_INFO', '')
   269         path = self.normpath(path)
   270 
   271         if path.startswith(self.prefix):
   272             return StaticApp(environ, start_response)
   273         else:
   274             return self.app(environ, start_response)
   275 
   276     def normpath(self, path):
   277         path2 = posixpath.normpath(urllib.unquote(path))
   278         if path.endswith("/"):
   279             path2 += "/"
   280         return path2
   281 
   282     
   283 class LogMiddleware:
   284     """WSGI middleware for logging the status."""
   285     def __init__(self, app):
   286         self.app = app
   287         self.format = '%s - - [%s] "%s %s %s" - %s'
   288     
   289         from BaseHTTPServer import BaseHTTPRequestHandler
   290         import StringIO
   291         f = StringIO.StringIO()
   292         
   293         class FakeSocket:
   294             def makefile(self, *a):
   295                 return f
   296         
   297         # take log_date_time_string method from BaseHTTPRequestHandler
   298         self.log_date_time_string = BaseHTTPRequestHandler(FakeSocket(), None, None).log_date_time_string
   299         
   300     def __call__(self, environ, start_response):
   301         def xstart_response(status, response_headers, *args):
   302             out = start_response(status, response_headers, *args)
   303             self.log(status, environ)
   304             return out
   305 
   306         return self.app(environ, xstart_response)
   307              
   308     def log(self, status, environ):
   309         outfile = environ.get('wsgi.errors', web.debug)
   310         req = environ.get('PATH_INFO', '_')
   311         protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-')
   312         method = environ.get('REQUEST_METHOD', '-')
   313         host = "%s:%s" % (environ.get('REMOTE_ADDR','-'), 
   314                           environ.get('REMOTE_PORT','-'))
   315 
   316         time = self.log_date_time_string()
   317 
   318         msg = self.format % (host, time, protocol, method, req, status)
   319         print >> outfile, utils.safestr(msg)