1""" 2SCGI-->WSGI application proxy, "SWAP". 3 4(Originally written by Titus Brown.) 5 6This lets an SCGI front-end like mod_scgi be used to execute WSGI 7application objects. To use it, subclass the SWAP class like so:: 8 9 class TestAppHandler(swap.SWAP): 10 def __init__(self, *args, **kwargs): 11 self.prefix = '/canal' 12 self.app_obj = TestAppClass 13 swap.SWAP.__init__(self, *args, **kwargs) 14 15where 'TestAppClass' is the application object from WSGI and '/canal' 16is the prefix for what is served by the SCGI Web-server-side process. 17 18Then execute the SCGI handler "as usual" by doing something like this:: 19 20 scgi_server.SCGIServer(TestAppHandler, port=4000).serve() 21 22and point mod_scgi (or whatever your SCGI front end is) at port 4000. 23 24Kudos to the WSGI folk for writing a nice PEP & the Quixote folk for 25writing a nice extensible SCGI server for Python! 26""" 27 28import six 29import sys 30import time 31from scgi import scgi_server 32 33def debug(msg): 34 timestamp = time.strftime("%Y-%m-%d %H:%M:%S", 35 time.localtime(time.time())) 36 sys.stderr.write("[%s] %s\n" % (timestamp, msg)) 37 38class SWAP(scgi_server.SCGIHandler): 39 """ 40 SCGI->WSGI application proxy: let an SCGI server execute WSGI 41 application objects. 42 """ 43 app_obj = None 44 prefix = None 45 46 def __init__(self, *args, **kwargs): 47 assert self.app_obj, "must set app_obj" 48 assert self.prefix is not None, "must set prefix" 49 args = (self,) + args 50 scgi_server.SCGIHandler.__init__(*args, **kwargs) 51 52 def handle_connection(self, conn): 53 """ 54 Handle an individual connection. 55 """ 56 input = conn.makefile("r") 57 output = conn.makefile("w") 58 59 environ = self.read_env(input) 60 environ['wsgi.input'] = input 61 environ['wsgi.errors'] = sys.stderr 62 environ['wsgi.version'] = (1, 0) 63 environ['wsgi.multithread'] = False 64 environ['wsgi.multiprocess'] = True 65 environ['wsgi.run_once'] = False 66 67 # dunno how SCGI does HTTPS signalling; can't test it myself... @CTB 68 if environ.get('HTTPS','off') in ('on','1'): 69 environ['wsgi.url_scheme'] = 'https' 70 else: 71 environ['wsgi.url_scheme'] = 'http' 72 73 ## SCGI does some weird environ manglement. We need to set 74 ## SCRIPT_NAME from 'prefix' and then set PATH_INFO from 75 ## REQUEST_URI. 76 77 prefix = self.prefix 78 path = environ['REQUEST_URI'][len(prefix):].split('?', 1)[0] 79 80 environ['SCRIPT_NAME'] = prefix 81 environ['PATH_INFO'] = path 82 83 headers_set = [] 84 headers_sent = [] 85 chunks = [] 86 def write(data): 87 chunks.append(data) 88 89 def start_response(status, response_headers, exc_info=None): 90 if exc_info: 91 try: 92 if headers_sent: 93 # Re-raise original exception if headers sent 94 six.reraise(exc_info[0], exc_info[1], exc_info[2]) 95 finally: 96 exc_info = None # avoid dangling circular ref 97 elif headers_set: 98 raise AssertionError("Headers already set!") 99 100 headers_set[:] = [status, response_headers] 101 return write 102 103 ### 104 105 result = self.app_obj(environ, start_response) 106 try: 107 for data in result: 108 chunks.append(data) 109 110 # Before the first output, send the stored headers 111 if not headers_set: 112 # Error -- the app never called start_response 113 status = '500 Server Error' 114 response_headers = [('Content-type', 'text/html')] 115 chunks = ["XXX start_response never called"] 116 else: 117 status, response_headers = headers_sent[:] = headers_set 118 119 output.write('Status: %s\r\n' % status) 120 for header in response_headers: 121 output.write('%s: %s\r\n' % header) 122 output.write('\r\n') 123 124 for data in chunks: 125 output.write(data) 126 finally: 127 if hasattr(result,'close'): 128 result.close() 129 130 # SCGI backends use connection closing to signal 'fini'. 131 try: 132 input.close() 133 output.close() 134 conn.close() 135 except IOError as err: 136 debug("IOError while closing connection ignored: %s" % err) 137 138 139def serve_application(application, prefix, port=None, host=None, max_children=None): 140 """ 141 Serve the specified WSGI application via SCGI proxy. 142 143 ``application`` 144 The WSGI application to serve. 145 146 ``prefix`` 147 The prefix for what is served by the SCGI Web-server-side process. 148 149 ``port`` 150 Optional port to bind the SCGI proxy to. Defaults to SCGIServer's 151 default port value. 152 153 ``host`` 154 Optional host to bind the SCGI proxy to. Defaults to SCGIServer's 155 default host value. 156 157 ``host`` 158 Optional maximum number of child processes the SCGIServer will 159 spawn. Defaults to SCGIServer's default max_children value. 160 """ 161 class SCGIAppHandler(SWAP): 162 def __init__ (self, *args, **kwargs): 163 self.prefix = prefix 164 self.app_obj = application 165 SWAP.__init__(self, *args, **kwargs) 166 167 kwargs = dict(handler_class=SCGIAppHandler) 168 for kwarg in ('host', 'port', 'max_children'): 169 if locals()[kwarg] is not None: 170 kwargs[kwarg] = locals()[kwarg] 171 172 scgi_server.SCGIServer(**kwargs).serve() 173