1 """Base classes for server/gateway implementations"""
3 from types
import StringType
4 from util
import FileWrapper
, guess_scheme
, is_hop_by_hop
5 from headers
import Headers
9 __all__
= ['BaseHandler', 'SimpleHandler', 'BaseCGIHandler', 'CGIHandler']
20 # Uncomment for 2.2 compatibility.
29 # Weekday and month names for HTTP date/time formatting; always English!
30 _weekdayname
= ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
31 _monthname
= [None, # Dummy so we can use 1-based month numbers
32 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
33 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
35 def format_date_time(timestamp
):
36 year
, month
, day
, hh
, mm
, ss
, wd
, y
, z
= time
.gmtime(timestamp
)
37 return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
38 _weekdayname
[wd
], day
, _monthname
[month
], year
, hh
, mm
, ss
44 """Manage the invocation of a WSGI application"""
46 # Configuration parameters; can override per-subclass or per-instance
48 wsgi_multithread
= True
49 wsgi_multiprocess
= True
52 origin_server
= True # We are transmitting direct to client
53 http_version
= "1.0" # Version that should be used for response
54 server_software
= None # String name of server software, if any
56 # os_environ is used to supply configuration from the OS environment:
57 # by default it's a copy of 'os.environ' as of import time, but you can
58 # override this in e.g. your __init__ method.
59 os_environ
= dict(os
.environ
.items())
61 # Collaborator classes
62 wsgi_file_wrapper
= FileWrapper
# set to None to disable
63 headers_class
= Headers
# must be a Headers-like class
65 # Error handling (also per-subclass or per-instance)
66 traceback_limit
= None # Print entire traceback to self.get_stderr()
67 error_status
= "500 Dude, this is whack!"
68 error_headers
= [('Content-Type','text/plain')]
69 error_body
= "A server error occurred. Please contact the administrator."
71 # State variables (don't mess with these)
72 status
= result
= None
84 def run(self
, application
):
85 """Invoke the application"""
86 # Note to self: don't move the close()! Asynchronous servers shouldn't
87 # call close() from finish_response(), so if you close() anywhere but
88 # the double-error branch here, you'll break asynchronous servers by
89 # prematurely closing. Async servers must return from 'run()' without
90 # closing if there might still be output to iterate over.
93 self
.result
= application(self
.environ
, self
.start_response
)
94 self
.finish_response()
99 # If we get an error handling an error, just give up already!
101 raise # ...and let the actual server figure it out.
104 def setup_environ(self
):
105 """Set up the environment for one request"""
107 env
= self
.environ
= self
.os_environ
.copy()
110 env
['wsgi.input'] = self
.get_stdin()
111 env
['wsgi.errors'] = self
.get_stderr()
112 env
['wsgi.version'] = self
.wsgi_version
113 env
['wsgi.run_once'] = self
.wsgi_run_once
114 env
['wsgi.url_scheme'] = self
.get_scheme()
115 env
['wsgi.multithread'] = self
.wsgi_multithread
116 env
['wsgi.multiprocess'] = self
.wsgi_multiprocess
118 if self
.wsgi_file_wrapper
is not None:
119 env
['wsgi.file_wrapper'] = self
.wsgi_file_wrapper
121 if self
.origin_server
and self
.server_software
:
122 env
.setdefault('SERVER_SOFTWARE',self
.server_software
)
125 def finish_response(self
):
126 """Send any iterable data, then close self and the iterable
128 Subclasses intended for use in asynchronous servers will
129 want to redefine this method, such that it sets up callbacks
130 in the event loop to iterate over the data, and to call
131 'self.close()' once the response is finished.
133 if not self
.result_is_file() or not self
.sendfile():
134 for data
in self
.result
:
136 self
.finish_content()
140 def get_scheme(self
):
141 """Return the URL scheme being used"""
142 return guess_scheme(self
.environ
)
145 def set_content_length(self
):
146 """Compute Content-Length or switch to chunked encoding if possible"""
148 blocks
= len(self
.result
)
149 except (TypeError,AttributeError,NotImplementedError):
153 self
.headers
['Content-Length'] = str(self
.bytes_sent
)
155 # XXX Try for chunked encoding if origin server and client is 1.1
158 def cleanup_headers(self
):
159 """Make any necessary header changes or defaults
161 Subclasses can extend this to add other defaults.
163 if 'Content-Length' not in self
.headers
:
164 self
.set_content_length()
166 def start_response(self
, status
, headers
,exc_info
=None):
167 """'start_response()' callable as specified by PEP 333"""
171 if self
.headers_sent
:
172 # Re-raise original exception if headers sent
173 raise exc_info
[0], exc_info
[1], exc_info
[2]
175 exc_info
= None # avoid dangling circular ref
176 elif self
.headers
is not None:
177 raise AssertionError("Headers already set!")
179 assert type(status
) is StringType
,"Status must be a string"
180 assert len(status
)>=4,"Status must be at least 4 characters"
181 assert int(status
[:3]),"Status message must begin w/3-digit code"
182 assert status
[3]==" ", "Status message must have a space after code"
184 for name
,val
in headers
:
185 assert type(name
) is StringType
,"Header names must be strings"
186 assert type(val
) is StringType
,"Header values must be strings"
187 assert not is_hop_by_hop(name
),"Hop-by-hop headers not allowed"
189 self
.headers
= self
.headers_class(headers
)
193 def send_preamble(self
):
194 """Transmit version/status/date/server, via self._write()"""
195 if self
.origin_server
:
196 if self
.client_is_modern():
197 self
._write
('HTTP/%s %s\r\n' % (self
.http_version
,self
.status
))
198 if 'Date' not in self
.headers
:
200 'Date: %s\r\n' % format_date_time(time
.time())
202 if self
.server_software
and 'Server' not in self
.headers
:
203 self
._write
('Server: %s\r\n' % self
.server_software
)
205 self
._write
('Status: %s\r\n' % self
.status
)
207 def write(self
, data
):
208 """'write()' callable as specified by PEP 333"""
210 assert type(data
) is StringType
,"write() argument must be string"
213 raise AssertionError("write() before start_response()")
215 elif not self
.headers_sent
:
216 # Before the first output, send the stored headers
217 self
.bytes_sent
= len(data
) # make sure we know content-length
220 self
.bytes_sent
+= len(data
)
222 # XXX check Content-Length and truncate if too many bytes written?
228 """Platform-specific file transmission
230 Override this method in subclasses to support platform-specific
231 file transmission. It is only called if the application's
232 return iterable ('self.result') is an instance of
233 'self.wsgi_file_wrapper'.
235 This method should return a true value if it was able to actually
236 transmit the wrapped file-like object using a platform-specific
237 approach. It should return a false value if normal iteration
238 should be used instead. An exception can be raised to indicate
239 that transmission was attempted, but failed.
241 NOTE: this method should call 'self.send_headers()' if
242 'self.headers_sent' is false and it is going to attempt direct
243 transmission of the file.
245 return False # No platform-specific transmission by default
248 def finish_content(self
):
249 """Ensure headers and content have both been sent"""
250 if not self
.headers_sent
:
251 self
.headers
['Content-Length'] = "0"
254 pass # XXX check if content-length was too short?
257 """Close the iterable (if needed) and reset all instance vars
259 Subclasses may want to also drop the client connection.
262 if hasattr(self
.result
,'close'):
265 self
.result
= self
.headers
= self
.status
= self
.environ
= None
266 self
.bytes_sent
= 0; self
.headers_sent
= False
269 def send_headers(self
):
270 """Transmit headers to the client, via self._write()"""
271 self
.cleanup_headers()
272 self
.headers_sent
= True
273 if not self
.origin_server
or self
.client_is_modern():
275 self
._write
(str(self
.headers
))
278 def result_is_file(self
):
279 """True if 'self.result' is an instance of 'self.wsgi_file_wrapper'"""
280 wrapper
= self
.wsgi_file_wrapper
281 return wrapper
is not None and isinstance(self
.result
,wrapper
)
284 def client_is_modern(self
):
285 """True if client can accept status and headers"""
286 return self
.environ
['SERVER_PROTOCOL'].upper() != 'HTTP/0.9'
289 def log_exception(self
,exc_info
):
290 """Log the 'exc_info' tuple in the server log
292 Subclasses may override to retarget the output or change its format.
295 from traceback
import print_exception
296 stderr
= self
.get_stderr()
298 exc_info
[0], exc_info
[1], exc_info
[2],
299 self
.traceback_limit
, stderr
305 def handle_error(self
):
306 """Log current error, and send error output to client if possible"""
307 self
.log_exception(sys
.exc_info())
308 if not self
.headers_sent
:
309 self
.result
= self
.error_output(self
.environ
, self
.start_response
)
310 self
.finish_response()
311 # XXX else: attempt advanced recovery techniques for HTML or text?
313 def error_output(self
, environ
, start_response
):
314 """WSGI mini-app to create error output
316 By default, this just uses the 'error_status', 'error_headers',
317 and 'error_body' attributes to generate an output page. It can
318 be overridden in a subclass to dynamically generate diagnostics,
319 choose an appropriate message for the user's preferred language, etc.
321 Note, however, that it's not recommended from a security perspective to
322 spit out diagnostics to any old user; ideally, you should have to do
323 something special to enable diagnostic output, which is why we don't
326 start_response(self
.error_status
,self
.error_headers
[:],sys
.exc_info())
327 return [self
.error_body
]
330 # Pure abstract methods; *must* be overridden in subclasses
332 def _write(self
,data
):
333 """Override in subclass to buffer data for send to client
335 It's okay if this method actually transmits the data; BaseHandler
336 just separates write and flush operations for greater efficiency
337 when the underlying system actually has such a distinction.
339 raise NotImplementedError
342 """Override in subclass to force sending of recent '_write()' calls
344 It's okay if this method is a no-op (i.e., if '_write()' actually
347 raise NotImplementedError
350 """Override in subclass to return suitable 'wsgi.input'"""
351 raise NotImplementedError
353 def get_stderr(self
):
354 """Override in subclass to return suitable 'wsgi.errors'"""
355 raise NotImplementedError
357 def add_cgi_vars(self
):
358 """Override in subclass to insert CGI variables in 'self.environ'"""
359 raise NotImplementedError
371 class SimpleHandler(BaseHandler
):
372 """Handler that's just initialized with streams, environment, etc.
374 This handler subclass is intended for synchronous HTTP/1.0 origin servers,
375 and handles sending the entire response output, given the correct inputs.
379 handler = SimpleHandler(
380 inp,out,err,env, multithread=False, multiprocess=True
384 def __init__(self
,stdin
,stdout
,stderr
,environ
,
385 multithread
=True, multiprocess
=False
390 self
.base_env
= environ
391 self
.wsgi_multithread
= multithread
392 self
.wsgi_multiprocess
= multiprocess
397 def get_stderr(self
):
400 def add_cgi_vars(self
):
401 self
.environ
.update(self
.base_env
)
403 def _write(self
,data
):
404 self
.stdout
.write(data
)
405 self
._write
= self
.stdout
.write
409 self
._flush
= self
.stdout
.flush
412 class BaseCGIHandler(SimpleHandler
):
414 """CGI-like systems using input/output/error streams and environ mapping
418 handler = BaseCGIHandler(inp,out,err,env)
421 This handler class is useful for gateway protocols like ReadyExec and
422 FastCGI, that have usable input/output/error streams and an environment
423 mapping. It's also the base class for CGIHandler, which just uses
424 sys.stdin, os.environ, and so on.
426 The constructor also takes keyword arguments 'multithread' and
427 'multiprocess' (defaulting to 'True' and 'False' respectively) to control
428 the configuration sent to the application. It sets 'origin_server' to
429 False (to enable CGI-like output), and assumes that 'wsgi.run_once' is
433 origin_server
= False
453 class CGIHandler(BaseCGIHandler
):
455 """CGI-based invocation via sys.stdin/stdout/stderr and os.environ
459 CGIHandler().run(app)
461 The difference between this class and BaseCGIHandler is that it always
462 uses 'wsgi.run_once' of 'True', 'wsgi.multithread' of 'False', and
463 'wsgi.multiprocess' of 'True'. It does not take any initialization
464 parameters, but always uses 'sys.stdin', 'os.environ', and friends.
466 If you need to override any of these parameters, use BaseCGIHandler
473 BaseCGIHandler
.__init
__(
474 self
, sys
.stdin
, sys
.stdout
, sys
.stderr
, dict(os
.environ
.items()),
475 multithread
=False, multiprocess
=True