diff options
author | Marc Abramowitz <marc@marc-abramowitz.com> | 2016-03-07 18:52:36 -0800 |
---|---|---|
committer | Marc Abramowitz <marc@marc-abramowitz.com> | 2016-03-07 18:52:36 -0800 |
commit | cc83e06efff71b81ca5a3ac6df65775971181295 (patch) | |
tree | d52fa3f1a93730f263c2c5ac8266de8e5fb12abf /paste/util/scgiserver.py | |
download | paste-git-tox_coverage.tar.gz |
tox.ini: Measure test coveragetox_coverage
Diffstat (limited to 'paste/util/scgiserver.py')
-rw-r--r-- | paste/util/scgiserver.py | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/paste/util/scgiserver.py b/paste/util/scgiserver.py new file mode 100644 index 0000000..1c86c86 --- /dev/null +++ b/paste/util/scgiserver.py @@ -0,0 +1,172 @@ +""" +SCGI-->WSGI application proxy, "SWAP". + +(Originally written by Titus Brown.) + +This lets an SCGI front-end like mod_scgi be used to execute WSGI +application objects. To use it, subclass the SWAP class like so:: + + class TestAppHandler(swap.SWAP): + def __init__(self, *args, **kwargs): + self.prefix = '/canal' + self.app_obj = TestAppClass + swap.SWAP.__init__(self, *args, **kwargs) + +where 'TestAppClass' is the application object from WSGI and '/canal' +is the prefix for what is served by the SCGI Web-server-side process. + +Then execute the SCGI handler "as usual" by doing something like this:: + + scgi_server.SCGIServer(TestAppHandler, port=4000).serve() + +and point mod_scgi (or whatever your SCGI front end is) at port 4000. + +Kudos to the WSGI folk for writing a nice PEP & the Quixote folk for +writing a nice extensible SCGI server for Python! +""" + +import six +import sys +import time +from scgi import scgi_server + +def debug(msg): + timestamp = time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(time.time())) + sys.stderr.write("[%s] %s\n" % (timestamp, msg)) + +class SWAP(scgi_server.SCGIHandler): + """ + SCGI->WSGI application proxy: let an SCGI server execute WSGI + application objects. + """ + app_obj = None + prefix = None + + def __init__(self, *args, **kwargs): + assert self.app_obj, "must set app_obj" + assert self.prefix is not None, "must set prefix" + args = (self,) + args + scgi_server.SCGIHandler.__init__(*args, **kwargs) + + def handle_connection(self, conn): + """ + Handle an individual connection. + """ + input = conn.makefile("r") + output = conn.makefile("w") + + environ = self.read_env(input) + environ['wsgi.input'] = input + environ['wsgi.errors'] = sys.stderr + environ['wsgi.version'] = (1, 0) + environ['wsgi.multithread'] = False + environ['wsgi.multiprocess'] = True + environ['wsgi.run_once'] = False + + # dunno how SCGI does HTTPS signalling; can't test it myself... @CTB + if environ.get('HTTPS','off') in ('on','1'): + environ['wsgi.url_scheme'] = 'https' + else: + environ['wsgi.url_scheme'] = 'http' + + ## SCGI does some weird environ manglement. We need to set + ## SCRIPT_NAME from 'prefix' and then set PATH_INFO from + ## REQUEST_URI. + + prefix = self.prefix + path = environ['REQUEST_URI'][len(prefix):].split('?', 1)[0] + + environ['SCRIPT_NAME'] = prefix + environ['PATH_INFO'] = path + + headers_set = [] + headers_sent = [] + chunks = [] + def write(data): + chunks.append(data) + + def start_response(status, response_headers, exc_info=None): + if exc_info: + try: + if headers_sent: + # Re-raise original exception if headers sent + six.reraise(exc_info[0], exc_info[1], exc_info[2]) + finally: + exc_info = None # avoid dangling circular ref + elif headers_set: + raise AssertionError("Headers already set!") + + headers_set[:] = [status, response_headers] + return write + + ### + + result = self.app_obj(environ, start_response) + try: + for data in result: + chunks.append(data) + + # Before the first output, send the stored headers + if not headers_set: + # Error -- the app never called start_response + status = '500 Server Error' + response_headers = [('Content-type', 'text/html')] + chunks = ["XXX start_response never called"] + else: + status, response_headers = headers_sent[:] = headers_set + + output.write('Status: %s\r\n' % status) + for header in response_headers: + output.write('%s: %s\r\n' % header) + output.write('\r\n') + + for data in chunks: + output.write(data) + finally: + if hasattr(result,'close'): + result.close() + + # SCGI backends use connection closing to signal 'fini'. + try: + input.close() + output.close() + conn.close() + except IOError as err: + debug("IOError while closing connection ignored: %s" % err) + + +def serve_application(application, prefix, port=None, host=None, max_children=None): + """ + Serve the specified WSGI application via SCGI proxy. + + ``application`` + The WSGI application to serve. + + ``prefix`` + The prefix for what is served by the SCGI Web-server-side process. + + ``port`` + Optional port to bind the SCGI proxy to. Defaults to SCGIServer's + default port value. + + ``host`` + Optional host to bind the SCGI proxy to. Defaults to SCGIServer's + default host value. + + ``host`` + Optional maximum number of child processes the SCGIServer will + spawn. Defaults to SCGIServer's default max_children value. + """ + class SCGIAppHandler(SWAP): + def __init__ (self, *args, **kwargs): + self.prefix = prefix + self.app_obj = application + SWAP.__init__(self, *args, **kwargs) + + kwargs = dict(handler_class=SCGIAppHandler) + for kwarg in ('host', 'port', 'max_children'): + if locals()[kwarg] is not None: + kwargs[kwarg] = locals()[kwarg] + + scgi_server.SCGIServer(**kwargs).serve() |