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