1
# Copyright (c) 2006 Allan Saddi <allan@saddi.com>
4
# Redistribution and use in source and binary forms, with or without
5
# modification, are permitted provided that the following conditions
7
# 1. Redistributions of source code must retain the above copyright
8
# notice, this list of conditions and the following disclaimer.
9
# 2. Redistributions in binary form must reproduce the above copyright
10
# notice, this list of conditions and the following disclaimer in the
11
# documentation and/or other materials provided with the distribution.
13
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25
# $Id: scgi_app.py 2111 2006-11-25 02:00:21Z asaddi $
27
__author__ = 'Allan Saddi <allan@saddi.com>'
28
__version__ = '$Revision: 2111 $'
37
def encodeNetstring(s):
38
return ''.join([str(len(s)), ':', s, ','])
40
class SCGIApp(object):
41
def __init__(self, connect=None, host=None, port=None,
44
assert port is not None
47
assert connect is not None
48
self._connect = connect
50
self._filterEnviron = filterEnviron
52
def __call__(self, environ, start_response):
53
sock = self._getConnection()
55
outfile = sock.makefile('w')
56
infile = sock.makefile('r')
60
# Filter WSGI environ and send as request headers
61
if self._filterEnviron:
62
headers = self._defaultFilterEnviron(environ)
64
headers = self._lightFilterEnviron(environ)
65
# TODO: Anything not from environ that needs to be sent also?
67
content_length = int(environ.get('CONTENT_LENGTH') or 0)
68
if headers.has_key('CONTENT_LENGTH'):
69
del headers['CONTENT_LENGTH']
71
headers_out = ['CONTENT_LENGTH', str(content_length), 'SCGI', '1']
72
for k,v in headers.items():
75
headers_out.append('') # For trailing NUL
76
outfile.write(encodeNetstring('\x00'.join(headers_out)))
78
# Transfer wsgi.input to outfile
80
chunk_size = min(content_length, 4096)
81
s = environ['wsgi.input'].read(chunk_size)
82
content_length -= len(s)
89
# Read result from SCGI server
92
buf = infile.read(4096)
99
result = ''.join(result)
101
# Parse response headers
106
eolpos = result.find('\n', pos)
108
line = result[pos:eolpos-1]
111
# strip in case of CR. NB: This will also strip other
115
# Empty line signifies end of headers
118
# TODO: Better error handling
119
header, value = line.split(':', 1)
120
header = header.strip().lower()
121
value = value.strip()
123
if header == 'status':
124
# Special handling of Status header
126
if status.find(' ') < 0:
127
# Append a dummy reason phrase if one was not provided
130
headers.append((header, value))
132
result = result[pos:]
134
# Set WSGI status, headers, and return result.
135
start_response(status, headers)
138
def _getConnection(self):
139
if type(self._connect) is str:
140
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
142
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
143
sock.connect(self._connect)
146
_environPrefixes = ['SERVER_', 'HTTP_', 'REQUEST_', 'REMOTE_', 'PATH_',
148
_environCopies = ['SCRIPT_NAME', 'QUERY_STRING', 'AUTH_TYPE']
151
def _defaultFilterEnviron(self, environ):
153
for n in environ.keys():
154
for p in self._environPrefixes:
156
result[n] = environ[n]
157
if n in self._environCopies:
158
result[n] = environ[n]
159
if n in self._environRenames:
160
result[self._environRenames[n]] = environ[n]
164
def _lightFilterEnviron(self, environ):
166
for n in environ.keys():
168
result[n] = environ[n]
171
if __name__ == '__main__':
172
from flup.server.ajp import WSGIServer
173
app = SCGIApp(connect=('localhost', 4000))
175
#app = paste.lint.middleware(app)
176
WSGIServer(app).run()