Back to index

moin  1.9.0~rc2
scgi_app.py
Go to the documentation of this file.
00001 # Copyright (c) 2006 Allan Saddi <allan@saddi.com>
00002 # All rights reserved.
00003 #
00004 # Redistribution and use in source and binary forms, with or without
00005 # modification, are permitted provided that the following conditions
00006 # are met:
00007 # 1. Redistributions of source code must retain the above copyright
00008 #    notice, this list of conditions and the following disclaimer.
00009 # 2. Redistributions in binary form must reproduce the above copyright
00010 #    notice, this list of conditions and the following disclaimer in the
00011 #    documentation and/or other materials provided with the distribution.
00012 #
00013 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
00014 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00015 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00016 # ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
00017 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
00018 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
00019 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
00020 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00021 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
00022 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
00023 # SUCH DAMAGE.
00024 #
00025 # $Id$
00026 
00027 __author__ = 'Allan Saddi <allan@saddi.com>'
00028 __version__ = '$Revision$'
00029 
00030 import select
00031 import struct
00032 import socket
00033 import errno
00034 
00035 __all__ = ['SCGIApp']
00036 
00037 def encodeNetstring(s):
00038     return ''.join([str(len(s)), ':', s, ','])
00039 
00040 class SCGIApp(object):
00041     def __init__(self, connect=None, host=None, port=None,
00042                  filterEnviron=True):
00043         if host is not None:
00044             assert port is not None
00045             connect=(host, port)
00046 
00047         assert connect is not None
00048         self._connect = connect
00049 
00050         self._filterEnviron = filterEnviron
00051         
00052     def __call__(self, environ, start_response):
00053         sock = self._getConnection()
00054 
00055         outfile = sock.makefile('w')
00056         infile = sock.makefile('r')
00057 
00058         sock.close()
00059 
00060         # Filter WSGI environ and send as request headers
00061         if self._filterEnviron:
00062             headers = self._defaultFilterEnviron(environ)
00063         else:
00064             headers = self._lightFilterEnviron(environ)
00065         # TODO: Anything not from environ that needs to be sent also?
00066 
00067         content_length = int(environ.get('CONTENT_LENGTH') or 0)
00068         if headers.has_key('CONTENT_LENGTH'):
00069             del headers['CONTENT_LENGTH']
00070             
00071         headers_out = ['CONTENT_LENGTH', str(content_length), 'SCGI', '1']
00072         for k,v in headers.items():
00073             headers_out.append(k)
00074             headers_out.append(v)
00075         headers_out.append('') # For trailing NUL
00076         outfile.write(encodeNetstring('\x00'.join(headers_out)))
00077 
00078         # Transfer wsgi.input to outfile
00079         while True:
00080             chunk_size = min(content_length, 4096)
00081             s = environ['wsgi.input'].read(chunk_size)
00082             content_length -= len(s)
00083             outfile.write(s)
00084 
00085             if not s: break
00086 
00087         outfile.close()
00088         
00089         # Read result from SCGI server
00090         result = []
00091         while True:
00092             buf = infile.read(4096)
00093             if not buf: break
00094 
00095             result.append(buf)
00096 
00097         infile.close()
00098         
00099         result = ''.join(result)
00100 
00101         # Parse response headers
00102         status = '200 OK'
00103         headers = []
00104         pos = 0
00105         while True:
00106             eolpos = result.find('\n', pos)
00107             if eolpos < 0: break
00108             line = result[pos:eolpos-1]
00109             pos = eolpos + 1
00110 
00111             # strip in case of CR. NB: This will also strip other
00112             # whitespace...
00113             line = line.strip()
00114             
00115             # Empty line signifies end of headers
00116             if not line: break
00117 
00118             # TODO: Better error handling
00119             header, value = line.split(':', 1)
00120             header = header.strip().lower()
00121             value = value.strip()
00122 
00123             if header == 'status':
00124                 # Special handling of Status header
00125                 status = value
00126                 if status.find(' ') < 0:
00127                     # Append a dummy reason phrase if one was not provided
00128                     status += ' SCGIApp'
00129             else:
00130                 headers.append((header, value))
00131 
00132         result = result[pos:]
00133 
00134         # Set WSGI status, headers, and return result.
00135         start_response(status, headers)
00136         return [result]
00137 
00138     def _getConnection(self):
00139         if type(self._connect) is str:
00140             sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
00141         else:
00142             sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
00143         sock.connect(self._connect)
00144         return sock
00145     
00146     _environPrefixes = ['SERVER_', 'HTTP_', 'REQUEST_', 'REMOTE_', 'PATH_',
00147                         'CONTENT_']
00148     _environCopies = ['SCRIPT_NAME', 'QUERY_STRING', 'AUTH_TYPE']
00149     _environRenames = {}
00150 
00151     def _defaultFilterEnviron(self, environ):
00152         result = {}
00153         for n in environ.keys():
00154             for p in self._environPrefixes:
00155                 if n.startswith(p):
00156                     result[n] = environ[n]
00157             if n in self._environCopies:
00158                 result[n] = environ[n]
00159             if n in self._environRenames:
00160                 result[self._environRenames[n]] = environ[n]
00161                 
00162         return result
00163 
00164     def _lightFilterEnviron(self, environ):
00165         result = {}
00166         for n in environ.keys():
00167             if n.upper() == n:
00168                 result[n] = environ[n]
00169         return result
00170 
00171 if __name__ == '__main__':
00172     from flup.server.ajp import WSGIServer
00173     app = SCGIApp(connect=('localhost', 4000))
00174     #import paste.lint
00175     #app = paste.lint.middleware(app)
00176     WSGIServer(app).run()