shalabh / quixote_extras
User contributed add-ons and for Quixote. See http://mems-exchange.org/software/quixote/. This repository used to be hosted at http://www.cafepy.com/quixote_extras/. All links from old location are now redirected here.
Clone this repository (size: 917.6 KB): HTTPS / SSH
$ hg clone http://bitbucket.org/shalabh/quixote_extras/
| commit 149: | 5b7911ccd017 |
| parent 148: | fbb05270db35 |
| branch: | default |
| tags: | tip |
Updated readme to reflect move to bitbucket.org
4 months ago
quixote_extras /
rex
/
wsgi_server.py
| r149:5b7911ccd017 | 243 loc | 9.4 KB | embed / history / annotate / raw / |
|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 | #!/usr/bin/env python
"""
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
###### QWIP: WSGI COMPATIBILITY WRAPPER FOR QUIXOTE #####################
class QWIP:
"""I make a Quixote Publisher object look like a WSGI application."""
request_class = HTTPRequest
def __init__(self, publisher):
self.publisher = publisher
def __call__(self, env, start_response):
"""I am called for each request."""
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.
###### WSGI REQUEST HANDLER FOR BaseHTTPServer ##########################
class WSGI_RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
"""Based on PEP 333 and Colin Stewart's WSGIHandler in WSGI Utils.
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)
###### THREAD SUPPORT ###################################################
# Internal functions that will be used as methods.
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.
class ThreadedPublisher(Publisher):
"""A thread-safe version of Quixote's 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 = {}
def make_publisher_thread_safe(publisher):
"""Modify an existing Publisher instance to make it compatible with
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
###### LAUNCH A SERVER ##################################################
def run(create_publisher, host='', port=80):
"""Launch the synchronous HTTP server."""
publisher = create_publisher()
httpd = BaseHTTPServer.HTTPServer((host, port), WSGI_RequestHandler)
httpd.application = QWIP(publisher)
httpd.serve_forever()
def run_multithreaded(create_publisher, host='', port=80):
"""Launch a multithreaded HTTP server."""
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()
###### MAIN ROUTINE #####################################################
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()
|
