A WSGI wrapper for Quixote applications, and a standalone interface to two HTTP servers.
To run the Quixote demos or your own application using the built-in synchronous HTTP server:
wsgi_server.py
wsgi_server.py --factory=quixote.demo.altdemo.create_publisher
wsgi_server.py --factory=quixote.demo.mini_demo.create_publisher
wsgi_server.py --help # Shows all options and defaults.
and point your browser to http://localhost:8080/ . The –factory option names a function that returns a Publisher object configured for the desired application.
To use a multi-threaded HTTP server instead, add the –thread option. The threaded server is from the ‘wsgiutils’ package, which is available at %s .
To use your Quixote application with any WSGI server or middleware:
from quixote.server.wsgi_server import QWIP
wsgi_application = QWIP(publisher)
# 'publisher' is a quixote.publish.Publisher instance or compatible.
MULTITHREADING ISSUES:
- The default Quixote Publisher is not thread safe.
- To make a thread safe publisher, use ThreadedPublisher or
make_publisher_thread_safe() below. See doc/multi-threaded.txt .
- QWIP will refuse to connect a multi-threaded server to an unsafe publisher.
It assumes safe publishers have an .is_thread_safe attribute that is true.
The default Quixote Publisher does not have this attribute, so is presumed
unsafe.
- Even if the publisher is thread safe, your application code or its dependent
modules may not be.
- Your create_publisher function has the best knowledge of whether the
publisher-application combination it’s returning is thread safe. So please
set the publisher.is_thread_safe instance variable to the correct value
before returning, because the default value may be wrong.
- ALL MULTITHREADING SUPPORT IN THIS MODULE IS EXPERIMENTAL AND SHOULD NOT BE
USED IN A PRODUCTION ENVIRONMENT WITHOUT THOROUGH TESTING!!!
The synchronous server (WSGI_HTTPRequestHandler) is also experimental.
Author: Mike Orr mso@oz.net.
Based on an earlier version of QWIP by Titus Brown titus@caltech.edu.
Last updated 2005-05-18.
import BaseHTTPServer, sys, thread, traceback, urlparse
from quixote.http_request import HTTPRequest
from quixote.publish import Publisher
from quixote.server.util import get_server_parser
from quixote.util import import_object
WSGIUTILS_URL = "http://www.owlfish.com/software/wsgiutils/"
__doc__ %= WSGIUTILS_URL
MAIN_DOC = """\
Publish a Quixote application using QWIP/WSGI and a synchronous or
multi-threaded HTTP server."""
THREAD_HELP = """\
Use a multi-threaded server and hack the Publisher to make it thread safe.
Depends on 'wsgiutils' package from %s .""" % WSGIUTILS_URL
class QWIP:
"""I make a Quixote Publisher object look like a WSGI application."""
request_class = HTTPRequest
def __init__(self, publisher):
self.publisher = publisher
I am called for each request.
def __call__(self, env, start_response):
if env.get('wsgi.multithread') and not \
getattr(self.publisher, 'is_thread_safe', False):
reason = "%r is not thread safe" % self.publisher
raise AssertionError(reason)
if not env.has_key('REQUEST_URI'):
env['REQUEST_URI'] = env['SCRIPT_NAME'] + env['PATH_INFO']
input = env['wsgi.input']
request = self.request_class(input, env)
response = self.publisher.process_request(request)
status = "%03d %s" % (response.status_code, response.reason_phrase)
headers = response.generate_headers()
start_response(status, headers)
return response.generate_body_chunks() # Iterable object.
Based on PEP 333 and Colin Stewart’s WSGIHandler in WSGI Utils.
class WSGI_RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
Assumes self.server.application is a WSGI application.
Doesn’t catch all possible exceptions; e.g., misformed headers.
def do_GET(self):
protocol, host, path, parameters, query, fragment = \
urlparse.urlparse("http://DUMMY" + self.path)
env = {
'wsgi.version': (1,0),
'wsgi.url_scheme': 'http',
'wsgi.input': self.rfile,
'wsgi.errors': sys.stderr,
'wsgi.multithread': False,
'wsgi.multiprocess': False,
'wsgi.run_once': False,
'REQUEST_METHOD': self.command,
'SCRIPT_NAME': '',
'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 header, value in self.headers.items():
header = 'HTTP_%s' % header.replace('-', '_').upper()
env[header] = value
self.status_code = None
self.status_message = None
self.headers = []
self.headers_sent = False
try:
result = self.server.application(env, self.start_response)
try:
for data in result:
if data: # Delay sending headers until first
self.write(data) # non-empty body element appears.
if not self.headers_sent:
self.write('') # If no body, send headers now.
finally:
if hasattr(result, 'close'):
result.close()
except:
self.log_exception(sys.exc_info())
do_POST = do_GET
def write(self, data):
assert self.headers, "write() before start_response()!"
if not self.headers_sent:
self.send_response(self.status_code, self.status_message)
for header, value in self.headers:
self.send_header(header, value)
self.end_headers()
self.headers_sent = True
self.wfile.write(data)
def start_response(self, status, headers_received, exc_info=None):
if exc_info:
self.log_exception(exc_info)
exc_info = None # Avoid dangling circular reference.
assert not self.headers, "Headers already set!"
status_code, status_message = status.split(' ', 1)
self.status_code = int(status_code)
self.status_message = status_message
self.headers = headers_received
return self.write
def log_exception(self, exc_info):
lines = traceback.format_exception(*exc_info)
message = ''.join(lines)
self.log_error(message)
def _set_request(self, request):
self._request_dict[thread.get_ident()] = request
def _clear_request(self):
import thread
try:
del self._request_dict[thread.get_ident()]
except KeyError:
pass
def get_request(self):
return self._request_dict.get(thread.get_ident())
Public classes and functions. A thread-safe version of Quixote’s Publisher.
class ThreadedPublisher(Publisher):
is_thread_safe = True
_set_request = _set_request
_clear_request = _clear_request
get_request = get_request
def __init__(self, *args, **kw):
Publisher.__init__(self, *args, **kw)
self._request_dict = {}
Modify an existing Publisher instance to make it compatible with
def make_publisher_thread_safe(publisher):
multithreaded servers. Side effects: replaces several methods in the instance’s class.
if getattr(publisher, 'is_thread_safe', False):
return
publisher._request_dict = {}
publisher.__class__._set_request = _set_request
publisher.__class__._clear_request = _clear_request
publisher.__class__.get_request = get_request
publisher.__class__.is_thread_safe = True
publisher.__class__._modified_by__make_publisher_web_safe = True
def run(create_publisher, host='', port=80):
publisher = create_publisher()
httpd = BaseHTTPServer.HTTPServer((host, port), WSGI_RequestHandler)
httpd.application = QWIP(publisher)
httpd.serve_forever()
Launch a multithreaded HTTP server.
def run_multithreaded(create_publisher, host='', port=80):
from wsgiutils.wsgiServer import WSGIServer
publisher = create_publisher()
make_publisher_thread_safe(publisher)
app_map = {'': QWIP(publisher)}
httpd = WSGIServer((host, port), app_map, serveFiles=False)
httpd.serve_forever()
def main():
parser = get_server_parser(MAIN_DOC)
parser.add_option('--thread', dest='thread', action='store_true',
help=THREAD_HELP)
options = parser.parse_args()[0]
factory = import_object(options.factory)
if options.thread:
run_multithreaded(factory, host=options.host, port=options.port)
else:
run(factory, host=options.host, port=options.port)
if __name__ == '__main__': main()