39
|
1 # -*- coding: utf-8 -*-
|
|
2 #
|
|
3 # Copyright (C) 2005 Edgewall Software
|
|
4 # Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de>
|
|
5 # All rights reserved.
|
|
6 #
|
|
7 # This software is licensed as described in the file COPYING, which
|
|
8 # you should have received as part of this distribution. The terms
|
|
9 # are also available at http://trac.edgewall.com/license.html.
|
|
10 #
|
|
11 # This software consists of voluntary contributions made by many
|
|
12 # individuals. For the exact contribution history, see the revision
|
|
13 # history and logs, available at http://projects.edgewall.com/trac/.
|
|
14 #
|
|
15 # Author: Christopher Lenz <cmlenz@gmx.de>
|
|
16
|
|
17 import sys
|
|
18 from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
|
|
19 from SocketServer import ForkingMixIn, ThreadingMixIn
|
|
20 import urllib
|
|
21
|
|
22
|
|
23 class _ErrorsWrapper(object):
|
|
24
|
|
25 def __init__(self, logfunc):
|
|
26 self.logfunc = logfunc
|
|
27
|
|
28 def flush(self):
|
|
29 pass
|
|
30
|
|
31 def write(self, msg):
|
|
32 self.logfunc(msg)
|
|
33
|
|
34 def writelines(self, seq):
|
|
35 map(self.write, seq)
|
|
36
|
|
37
|
|
38 class _FileWrapper(object):
|
|
39 """Wrapper for sending a file as response."""
|
|
40
|
|
41 def __init__(self, fileobj, blocksize=None):
|
|
42 self.fileobj = fileobj
|
|
43 self.blocksize = blocksize
|
|
44 self.read = self.fileobj.read
|
|
45 if hasattr(fileobj, 'close'):
|
|
46 self.close = fileobj.close
|
|
47
|
|
48 def __iter__(self):
|
|
49 return self
|
|
50
|
|
51 def next(self):
|
|
52 data = self.fileobj.read(self.blocksize)
|
|
53 if not data:
|
|
54 raise StopIteration
|
|
55 return data
|
|
56
|
|
57
|
|
58 class WSGIGateway(object):
|
|
59 """Abstract base class for WSGI servers or gateways."""
|
|
60
|
|
61 wsgi_version = (1, 0)
|
|
62 wsgi_multithread = True
|
|
63 wsgi_multiprocess = True
|
|
64 wsgi_run_once = False
|
|
65 wsgi_file_wrapper = _FileWrapper
|
|
66
|
|
67 def __init__(self, environ, stdin=sys.stdin, stderr=sys.stderr):
|
|
68 """Initialize the gateway object."""
|
|
69 environ['wsgi.version'] = self.wsgi_version
|
|
70 environ['wsgi.url_scheme'] = 'http'
|
|
71 if environ.get('HTTPS', '').lower() in ('yes', 'on', '1'):
|
|
72 environ['wsgi.url_scheme'] = 'https'
|
|
73 environ['wsgi.input'] = stdin
|
|
74 environ['wsgi.errors'] = stderr
|
|
75 environ['wsgi.multithread'] = self.wsgi_multithread
|
|
76 environ['wsgi.multiprocess'] = self.wsgi_multiprocess
|
|
77 environ['wsgi.run_once'] = self.wsgi_run_once
|
|
78 if self.wsgi_file_wrapper is not None:
|
|
79 environ['wsgi.file_wrapper'] = self.wsgi_file_wrapper
|
|
80 self.environ = environ
|
|
81
|
|
82 self.headers_set = []
|
|
83 self.headers_sent = []
|
|
84
|
|
85 def run(self, application):
|
|
86 """Start the gateway with the given WSGI application."""
|
|
87 response = application(self.environ, self._start_response)
|
|
88 try:
|
|
89 if isinstance(response, self.wsgi_file_wrapper) \
|
|
90 and hasattr(self, '_sendfile'):
|
|
91 self._sendfile(response.fileobj)
|
|
92 else:
|
|
93 for chunk in response:
|
|
94 if chunk:
|
|
95 self._write(chunk)
|
|
96 if not self.headers_sent:
|
|
97 self._write('')
|
|
98 finally:
|
|
99 if hasattr(response, 'close'):
|
|
100 response.close()
|
|
101
|
|
102 def _start_response(self, status, headers, exc_info=None):
|
|
103 """Callback for starting a HTTP response."""
|
|
104 if exc_info:
|
|
105 try:
|
|
106 if self.headers_sent: # Re-raise original exception
|
|
107 raise exc_info[0], exc_info[1], exc_info[2]
|
|
108 finally:
|
|
109 exc_info = None # avoid dangling circular ref
|
|
110 else:
|
|
111 assert not self.headers_set, 'Response already started'
|
|
112
|
|
113 self.headers_set = [status, headers]
|
|
114 return self._write
|
|
115
|
|
116 def _write(self, data):
|
|
117 """Callback for writing data to the response.
|
|
118
|
|
119 Concrete subclasses must implement this method."""
|
|
120 raise NotImplementedError
|
|
121
|
|
122
|
|
123 class WSGIRequestHandler(BaseHTTPRequestHandler):
|
|
124
|
|
125 def setup_environ(self):
|
|
126 self.raw_requestline = self.rfile.readline()
|
|
127 if not self.parse_request(): # An error code has been sent, just exit
|
|
128 self.close_connection = 1
|
|
129 return
|
|
130
|
|
131 environ = self.server.environ.copy()
|
|
132 environ['SERVER_PROTOCOL'] = self.request_version
|
|
133 environ['REQUEST_METHOD'] = self.command
|
|
134
|
|
135 if '?' in self.path:
|
|
136 path_info, query_string = self.path.split('?', 1)
|
|
137 else:
|
|
138 path_info, query_string = self.path, ''
|
|
139 environ['PATH_INFO'] = urllib.unquote(path_info)
|
|
140 environ['QUERY_STRING'] = query_string
|
|
141
|
|
142 host = self.address_string()
|
|
143 if host != self.client_address[0]:
|
|
144 environ['REMOTE_HOST'] = host
|
|
145 environ['REMOTE_ADDR'] = self.client_address[0]
|
|
146
|
|
147 if self.headers.typeheader is None:
|
|
148 environ['CONTENT_TYPE'] = self.headers.type
|
|
149 else:
|
|
150 environ['CONTENT_TYPE'] = self.headers.typeheader
|
|
151
|
|
152 length = self.headers.getheader('content-length')
|
|
153 if length:
|
|
154 environ['CONTENT_LENGTH'] = length
|
|
155
|
|
156 for name, value in [header.split(':', 1) for header
|
|
157 in self.headers.headers]:
|
|
158 name = name.replace('-', '_').upper();
|
|
159 value = value.strip()
|
|
160 if name in environ:
|
|
161 # skip content length, type, etc.
|
|
162 continue
|
|
163 if 'HTTP_' + name in environ:
|
|
164 # comma-separate multiple headers
|
|
165 environ['HTTP_' + name] += ',' + value
|
|
166 else:
|
|
167 environ['HTTP_' + name] = value
|
|
168
|
|
169 return environ
|
|
170
|
|
171 def handle_one_request(self):
|
|
172 environ = self.setup_environ()
|
|
173 gateway = self.server.gateway(self, environ)
|
|
174 gateway.run(self.server.application)
|
|
175
|
|
176 def finish(self):
|
|
177 """We need to help the garbage collector a little."""
|
|
178 BaseHTTPRequestHandler.finish(self)
|
|
179 self.wfile = None
|
|
180 self.rfile = None
|
|
181
|
|
182
|
|
183 class WSGIServerGateway(WSGIGateway):
|
|
184
|
|
185 def __init__(self, handler, environ):
|
|
186 WSGIGateway.__init__(self, environ, handler.rfile,
|
|
187 _ErrorsWrapper(lambda x: handler.log_error('%s', x)))
|
|
188 self.handler = handler
|
|
189
|
|
190 def _write(self, data):
|
|
191 assert self.headers_set, 'Response not started'
|
|
192
|
|
193 if not self.headers_sent:
|
|
194 status, headers = self.headers_sent = self.headers_set
|
|
195 self.handler.send_response(int(status[:3]))
|
|
196 for name, value in headers:
|
|
197 self.handler.send_header(name, value)
|
|
198 self.handler.end_headers()
|
|
199 self.handler.wfile.write(data)
|
|
200
|
|
201
|
|
202 class WSGIServer(HTTPServer):
|
|
203
|
|
204 def __init__(self, server_address, application, gateway=WSGIServerGateway,
|
|
205 request_handler=WSGIRequestHandler):
|
|
206 HTTPServer.__init__(self, server_address, request_handler)
|
|
207
|
|
208 self.application = application
|
|
209
|
|
210 gateway.wsgi_multithread = isinstance(self, ThreadingMixIn)
|
|
211 gateway.wsgi_multiprocess = isinstance(self, ForkingMixIn)
|
|
212 self.gateway = gateway
|
|
213
|
|
214 self.environ = {'SERVER_NAME': self.server_name,
|
|
215 'SERVER_PORT': str(self.server_port),
|
|
216 'SCRIPT_NAME': ''}
|