Mercurial > genshi > mirror
comparison examples/trac/trac/web/wsgi.py @ 39:93b4dcbafd7b trunk
Copy Trac to main branch.
author | cmlenz |
---|---|
date | Mon, 03 Jul 2006 18:53:27 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
38:ee669cb9cccc | 39:93b4dcbafd7b |
---|---|
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': ''} |