__all__ = ["runsimple"] import sys, os from SimpleHTTPServer import SimpleHTTPRequestHandler import urllib import posixpath import webapi as web import net import utils def runbasic(func, server_address=("0.0.0.0", 8080)): """ Runs a simple HTTP server hosting WSGI app `func`. The directory `static/` is hosted statically. Based on [WsgiServer][ws] from [Colin Stewart][cs]. [ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html [cs]: http://www.owlfish.com/ """ # Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/) # Modified somewhat for simplicity # Used under the modified BSD license: # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse import socket, errno import traceback class WSGIHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): def run_wsgi_app(self): protocol, host, path, parameters, query, fragment = \ urlparse.urlparse('http://dummyhost%s' % self.path) # we only use path, query env = {'wsgi.version': (1, 0) ,'wsgi.url_scheme': 'http' ,'wsgi.input': self.rfile ,'wsgi.errors': sys.stderr ,'wsgi.multithread': 1 ,'wsgi.multiprocess': 0 ,'wsgi.run_once': 0 ,'REQUEST_METHOD': self.command ,'REQUEST_URI': self.path ,'PATH_INFO': path ,'QUERY_STRING': query ,'CONTENT_TYPE': self.headers.get('Content-Type', '') ,'CONTENT_LENGTH': self.headers.get('Content-Length', '') ,'REMOTE_ADDR': self.client_address[0] ,'SERVER_NAME': self.server.server_address[0] ,'SERVER_PORT': str(self.server.server_address[1]) ,'SERVER_PROTOCOL': self.request_version } for http_header, http_value in self.headers.items(): env ['HTTP_%s' % http_header.replace('-', '_').upper()] = \ http_value # Setup the state self.wsgi_sent_headers = 0 self.wsgi_headers = [] try: # We have there environment, now invoke the application result = self.server.app(env, self.wsgi_start_response) try: try: for data in result: if data: self.wsgi_write_data(data) finally: if hasattr(result, 'close'): result.close() except socket.error, socket_err: # Catch common network errors and suppress them if (socket_err.args[0] in \ (errno.ECONNABORTED, errno.EPIPE)): return except socket.timeout, socket_timeout: return except: print >> web.debug, traceback.format_exc(), if (not self.wsgi_sent_headers): # We must write out something! self.wsgi_write_data(" ") return do_POST = run_wsgi_app do_PUT = run_wsgi_app do_DELETE = run_wsgi_app def do_GET(self): if self.path.startswith('/static/'): SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) else: self.run_wsgi_app() def wsgi_start_response(self, response_status, response_headers, exc_info=None): if (self.wsgi_sent_headers): raise Exception \ ("Headers already sent and start_response called again!") # Should really take a copy to avoid changes in the application.... self.wsgi_headers = (response_status, response_headers) return self.wsgi_write_data def wsgi_write_data(self, data): if (not self.wsgi_sent_headers): status, headers = self.wsgi_headers # Need to send header prior to data status_code = status[:status.find(' ')] status_msg = status[status.find(' ') + 1:] self.send_response(int(status_code), status_msg) for header, value in headers: self.send_header(header, value) self.end_headers() self.wsgi_sent_headers = 1 # Send the data self.wfile.write(data) class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): def __init__(self, func, server_address): BaseHTTPServer.HTTPServer.__init__(self, server_address, WSGIHandler) self.app = func self.serverShuttingDown = 0 print "http://%s:%d/" % server_address WSGIServer(func, server_address).serve_forever() # The WSGIServer instance. # Made global so that it can be stopped in embedded mode. server = None def runsimple(func, server_address=("0.0.0.0", 8080)): """ Runs [CherryPy][cp] WSGI server hosting WSGI app `func`. The directory `static/` is hosted statically. [cp]: http://www.cherrypy.org """ global server func = StaticMiddleware(func) func = LogMiddleware(func) server = WSGIServer(server_address, func) if server.ssl_adapter: print "https://%s:%d/" % server_address else: print "http://%s:%d/" % server_address try: server.start() except (KeyboardInterrupt, SystemExit): server.stop() server = None def WSGIServer(server_address, wsgi_app): """Creates CherryPy WSGI server listening at `server_address` to serve `wsgi_app`. This function can be overwritten to customize the webserver or use a different webserver. """ import wsgiserver # Default values of wsgiserver.ssl_adapters uses cherrypy.wsgiserver # prefix. Overwriting it make it work with web.wsgiserver. wsgiserver.ssl_adapters = { 'builtin': 'web.wsgiserver.ssl_builtin.BuiltinSSLAdapter', 'pyopenssl': 'web.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter', } server = wsgiserver.CherryPyWSGIServer(server_address, wsgi_app, server_name="localhost") def create_ssl_adapter(cert, key): # wsgiserver tries to import submodules as cherrypy.wsgiserver.foo. # That doesn't work as not it is web.wsgiserver. # Patching sys.modules temporarily to make it work. import types cherrypy = types.ModuleType('cherrypy') cherrypy.wsgiserver = wsgiserver sys.modules['cherrypy'] = cherrypy sys.modules['cherrypy.wsgiserver'] = wsgiserver from wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter adapter = pyOpenSSLAdapter(cert, key) # We are done with our work. Cleanup the patches. del sys.modules['cherrypy'] del sys.modules['cherrypy.wsgiserver'] return adapter # SSL backward compatibility if (server.ssl_adapter is None and getattr(server, 'ssl_certificate', None) and getattr(server, 'ssl_private_key', None)): server.ssl_adapter = create_ssl_adapter(server.ssl_certificate, server.ssl_private_key) server.nodelay = not sys.platform.startswith('java') # TCP_NODELAY isn't supported on the JVM return server class StaticApp(SimpleHTTPRequestHandler): """WSGI application for serving static files.""" def __init__(self, environ, start_response): self.headers = [] self.environ = environ self.start_response = start_response def send_response(self, status, msg=""): self.status = str(status) + " " + msg def send_header(self, name, value): self.headers.append((name, value)) def end_headers(self): pass def log_message(*a): pass def __iter__(self): environ = self.environ self.path = environ.get('PATH_INFO', '') self.client_address = environ.get('REMOTE_ADDR','-'), \ environ.get('REMOTE_PORT','-') self.command = environ.get('REQUEST_METHOD', '-') from cStringIO import StringIO self.wfile = StringIO() # for capturing error try: path = self.translate_path(self.path) etag = '"%s"' % os.path.getmtime(path) client_etag = environ.get('HTTP_IF_NONE_MATCH') self.send_header('ETag', etag) if etag == client_etag: self.send_response(304, "Not Modified") self.start_response(self.status, self.headers) raise StopIteration except OSError: pass # Probably a 404 f = self.send_head() self.start_response(self.status, self.headers) if f: block_size = 16 * 1024 while True: buf = f.read(block_size) if not buf: break yield buf f.close() else: value = self.wfile.getvalue() yield value class StaticMiddleware: """WSGI middleware for serving static files.""" def __init__(self, app, prefix='/static/'): self.app = app self.prefix = prefix def __call__(self, environ, start_response): path = environ.get('PATH_INFO', '') path = self.normpath(path) if path.startswith(self.prefix): return StaticApp(environ, start_response) else: return self.app(environ, start_response) def normpath(self, path): path2 = posixpath.normpath(urllib.unquote(path)) if path.endswith("/"): path2 += "/" return path2 class LogMiddleware: """WSGI middleware for logging the status.""" def __init__(self, app): self.app = app self.format = '%s - - [%s] "%s %s %s" - %s' from BaseHTTPServer import BaseHTTPRequestHandler import StringIO f = StringIO.StringIO() class FakeSocket: def makefile(self, *a): return f # take log_date_time_string method from BaseHTTPRequestHandler self.log_date_time_string = BaseHTTPRequestHandler(FakeSocket(), None, None).log_date_time_string def __call__(self, environ, start_response): def xstart_response(status, response_headers, *args): out = start_response(status, response_headers, *args) self.log(status, environ) return out return self.app(environ, xstart_response) def log(self, status, environ): outfile = environ.get('wsgi.errors', web.debug) req = environ.get('PATH_INFO', '_') protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-') method = environ.get('REQUEST_METHOD', '-') host = "%s:%s" % (environ.get('REMOTE_ADDR','-'), environ.get('REMOTE_PORT','-')) time = self.log_date_time_string() msg = self.format % (host, time, protocol, method, req, status) print >> outfile, utils.safestr(msg)