home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / lib / python2.5 / wsgiref / handlers.py < prev    next >
Encoding:
Python Source  |  2008-10-05  |  15.2 KB  |  493 lines

  1. """Base classes for server/gateway implementations"""
  2.  
  3. from types import StringType
  4. from util import FileWrapper, guess_scheme, is_hop_by_hop
  5. from headers import Headers
  6.  
  7. import sys, os, time
  8.  
  9. __all__ = ['BaseHandler', 'SimpleHandler', 'BaseCGIHandler', 'CGIHandler']
  10.  
  11. try:
  12.     dict
  13. except NameError:
  14.     def dict(items):
  15.         d = {}
  16.         for k,v in items:
  17.             d[k] = v
  18.         return d
  19.  
  20. try:
  21.     True
  22.     False
  23. except NameError:
  24.     True = not None
  25.     False = not True
  26.  
  27.  
  28. # Weekday and month names for HTTP date/time formatting; always English!
  29. _weekdayname = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
  30. _monthname = [None, # Dummy so we can use 1-based month numbers
  31.               "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  32.               "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
  33.  
  34. def format_date_time(timestamp):
  35.     year, month, day, hh, mm, ss, wd, y, z = time.gmtime(timestamp)
  36.     return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
  37.         _weekdayname[wd], day, _monthname[month], year, hh, mm, ss
  38.     )
  39.  
  40.  
  41.  
  42. class BaseHandler:
  43.     """Manage the invocation of a WSGI application"""
  44.  
  45.     # Configuration parameters; can override per-subclass or per-instance
  46.     wsgi_version = (1,0)
  47.     wsgi_multithread = True
  48.     wsgi_multiprocess = True
  49.     wsgi_run_once = False
  50.  
  51.     origin_server = True    # We are transmitting direct to client
  52.     http_version  = "1.0"   # Version that should be used for response
  53.     server_software = None  # String name of server software, if any
  54.  
  55.     # os_environ is used to supply configuration from the OS environment:
  56.     # by default it's a copy of 'os.environ' as of import time, but you can
  57.     # override this in e.g. your __init__ method.
  58.     os_environ = dict(os.environ.items())
  59.  
  60.     # Collaborator classes
  61.     wsgi_file_wrapper = FileWrapper     # set to None to disable
  62.     headers_class = Headers             # must be a Headers-like class
  63.  
  64.     # Error handling (also per-subclass or per-instance)
  65.     traceback_limit = None  # Print entire traceback to self.get_stderr()
  66.     error_status = "500 Dude, this is whack!"
  67.     error_headers = [('Content-Type','text/plain')]
  68.     error_body = "A server error occurred.  Please contact the administrator."
  69.  
  70.     # State variables (don't mess with these)
  71.     status = result = None
  72.     headers_sent = False
  73.     headers = None
  74.     bytes_sent = 0
  75.  
  76.  
  77.  
  78.  
  79.  
  80.  
  81.  
  82.  
  83.     def run(self, application):
  84.         """Invoke the application"""
  85.         # Note to self: don't move the close()!  Asynchronous servers shouldn't
  86.         # call close() from finish_response(), so if you close() anywhere but
  87.         # the double-error branch here, you'll break asynchronous servers by
  88.         # prematurely closing.  Async servers must return from 'run()' without
  89.         # closing if there might still be output to iterate over.
  90.         try:
  91.             self.setup_environ()
  92.             self.result = application(self.environ, self.start_response)
  93.             self.finish_response()
  94.         except:
  95.             try:
  96.                 self.handle_error()
  97.             except:
  98.                 # If we get an error handling an error, just give up already!
  99.                 self.close()
  100.                 raise   # ...and let the actual server figure it out.
  101.  
  102.  
  103.     def setup_environ(self):
  104.         """Set up the environment for one request"""
  105.  
  106.         env = self.environ = self.os_environ.copy()
  107.         self.add_cgi_vars()
  108.  
  109.         env['wsgi.input']        = self.get_stdin()
  110.         env['wsgi.errors']       = self.get_stderr()
  111.         env['wsgi.version']      = self.wsgi_version
  112.         env['wsgi.run_once']     = self.wsgi_run_once
  113.         env['wsgi.url_scheme']   = self.get_scheme()
  114.         env['wsgi.multithread']  = self.wsgi_multithread
  115.         env['wsgi.multiprocess'] = self.wsgi_multiprocess
  116.  
  117.         if self.wsgi_file_wrapper is not None:
  118.             env['wsgi.file_wrapper'] = self.wsgi_file_wrapper
  119.  
  120.         if self.origin_server and self.server_software:
  121.             env.setdefault('SERVER_SOFTWARE',self.server_software)
  122.  
  123.  
  124.     def finish_response(self):
  125.         """Send any iterable data, then close self and the iterable
  126.  
  127.         Subclasses intended for use in asynchronous servers will
  128.         want to redefine this method, such that it sets up callbacks
  129.         in the event loop to iterate over the data, and to call
  130.         'self.close()' once the response is finished.
  131.         """
  132.         if not self.result_is_file() or not self.sendfile():
  133.             for data in self.result:
  134.                 self.write(data)
  135.             self.finish_content()
  136.         self.close()
  137.  
  138.  
  139.     def get_scheme(self):
  140.         """Return the URL scheme being used"""
  141.         return guess_scheme(self.environ)
  142.  
  143.  
  144.     def set_content_length(self):
  145.         """Compute Content-Length or switch to chunked encoding if possible"""
  146.         try:
  147.             blocks = len(self.result)
  148.         except (TypeError,AttributeError,NotImplementedError):
  149.             pass
  150.         else:
  151.             if blocks==1:
  152.                 self.headers['Content-Length'] = str(self.bytes_sent)
  153.                 return
  154.         # XXX Try for chunked encoding if origin server and client is 1.1
  155.  
  156.  
  157.     def cleanup_headers(self):
  158.         """Make any necessary header changes or defaults
  159.  
  160.         Subclasses can extend this to add other defaults.
  161.         """
  162.         if not self.headers.has_key('Content-Length'):
  163.             self.set_content_length()
  164.  
  165.     def start_response(self, status, headers,exc_info=None):
  166.         """'start_response()' callable as specified by PEP 333"""
  167.  
  168.         if exc_info:
  169.             try:
  170.                 if self.headers_sent:
  171.                     # Re-raise original exception if headers sent
  172.                     raise exc_info[0], exc_info[1], exc_info[2]
  173.             finally:
  174.                 exc_info = None        # avoid dangling circular ref
  175.         elif self.headers is not None:
  176.             raise AssertionError("Headers already set!")
  177.  
  178.         assert type(status) is StringType,"Status must be a string"
  179.         assert len(status)>=4,"Status must be at least 4 characters"
  180.         assert int(status[:3]),"Status message must begin w/3-digit code"
  181.         assert status[3]==" ", "Status message must have a space after code"
  182.         if __debug__:
  183.             for name,val in headers:
  184.                 assert type(name) is StringType,"Header names must be strings"
  185.                 assert type(val) is StringType,"Header values must be strings"
  186.                 assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed"
  187.         self.status = status
  188.         self.headers = self.headers_class(headers)
  189.         return self.write
  190.  
  191.  
  192.     def send_preamble(self):
  193.         """Transmit version/status/date/server, via self._write()"""
  194.         if self.origin_server:
  195.             if self.client_is_modern():
  196.                 self._write('HTTP/%s %s\r\n' % (self.http_version,self.status))
  197.                 if not self.headers.has_key('Date'):
  198.                     self._write(
  199.                         'Date: %s\r\n' % format_date_time(time.time())
  200.                     )
  201.                 if self.server_software and not self.headers.has_key('Server'):
  202.                     self._write('Server: %s\r\n' % self.server_software)
  203.         else:
  204.             self._write('Status: %s\r\n' % self.status)
  205.  
  206.     def write(self, data):
  207.         """'write()' callable as specified by PEP 333"""
  208.  
  209.         assert type(data) is StringType,"write() argument must be string"
  210.  
  211.         if not self.status:
  212.             raise AssertionError("write() before start_response()")
  213.  
  214.         elif not self.headers_sent:
  215.             # Before the first output, send the stored headers
  216.             self.bytes_sent = len(data)    # make sure we know content-length
  217.             self.send_headers()
  218.         else:
  219.             self.bytes_sent += len(data)
  220.  
  221.         # XXX check Content-Length and truncate if too many bytes written?
  222.         self._write(data)
  223.         self._flush()
  224.  
  225.  
  226.     def sendfile(self):
  227.         """Platform-specific file transmission
  228.  
  229.         Override this method in subclasses to support platform-specific
  230.         file transmission.  It is only called if the application's
  231.         return iterable ('self.result') is an instance of
  232.         'self.wsgi_file_wrapper'.
  233.  
  234.         This method should return a true value if it was able to actually
  235.         transmit the wrapped file-like object using a platform-specific
  236.         approach.  It should return a false value if normal iteration
  237.         should be used instead.  An exception can be raised to indicate
  238.         that transmission was attempted, but failed.
  239.  
  240.         NOTE: this method should call 'self.send_headers()' if
  241.         'self.headers_sent' is false and it is going to attempt direct
  242.         transmission of the file.
  243.         """
  244.         return False   # No platform-specific transmission by default
  245.  
  246.  
  247.     def finish_content(self):
  248.         """Ensure headers and content have both been sent"""
  249.         if not self.headers_sent:
  250.             self.headers['Content-Length'] = "0"
  251.             self.send_headers()
  252.         else:
  253.             pass # XXX check if content-length was too short?
  254.  
  255.     def close(self):
  256.         """Close the iterable (if needed) and reset all instance vars
  257.  
  258.         Subclasses may want to also drop the client connection.
  259.         """
  260.         try:
  261.             if hasattr(self.result,'close'):
  262.                 self.result.close()
  263.         finally:
  264.             self.result = self.headers = self.status = self.environ = None
  265.             self.bytes_sent = 0; self.headers_sent = False
  266.  
  267.  
  268.     def send_headers(self):
  269.         """Transmit headers to the client, via self._write()"""
  270.         self.cleanup_headers()
  271.         self.headers_sent = True
  272.         if not self.origin_server or self.client_is_modern():
  273.             self.send_preamble()
  274.             self._write(str(self.headers))
  275.  
  276.  
  277.     def result_is_file(self):
  278.         """True if 'self.result' is an instance of 'self.wsgi_file_wrapper'"""
  279.         wrapper = self.wsgi_file_wrapper
  280.         return wrapper is not None and isinstance(self.result,wrapper)
  281.  
  282.  
  283.     def client_is_modern(self):
  284.         """True if client can accept status and headers"""
  285.         return self.environ['SERVER_PROTOCOL'].upper() != 'HTTP/0.9'
  286.  
  287.  
  288.     def log_exception(self,exc_info):
  289.         """Log the 'exc_info' tuple in the server log
  290.  
  291.         Subclasses may override to retarget the output or change its format.
  292.         """
  293.         try:
  294.             from traceback import print_exception
  295.             stderr = self.get_stderr()
  296.             print_exception(
  297.                 exc_info[0], exc_info[1], exc_info[2],
  298.                 self.traceback_limit, stderr
  299.             )
  300.             stderr.flush()
  301.         finally:
  302.             exc_info = None
  303.  
  304.     def handle_error(self):
  305.         """Log current error, and send error output to client if possible"""
  306.         self.log_exception(sys.exc_info())
  307.         if not self.headers_sent:
  308.             self.result = self.error_output(self.environ, self.start_response)
  309.             self.finish_response()
  310.         # XXX else: attempt advanced recovery techniques for HTML or text?
  311.  
  312.     def error_output(self, environ, start_response):
  313.         """WSGI mini-app to create error output
  314.  
  315.         By default, this just uses the 'error_status', 'error_headers',
  316.         and 'error_body' attributes to generate an output page.  It can
  317.         be overridden in a subclass to dynamically generate diagnostics,
  318.         choose an appropriate message for the user's preferred language, etc.
  319.  
  320.         Note, however, that it's not recommended from a security perspective to
  321.         spit out diagnostics to any old user; ideally, you should have to do
  322.         something special to enable diagnostic output, which is why we don't
  323.         include any here!
  324.         """
  325.         start_response(self.error_status,self.error_headers[:],sys.exc_info())
  326.         return [self.error_body]
  327.  
  328.  
  329.     # Pure abstract methods; *must* be overridden in subclasses
  330.  
  331.     def _write(self,data):
  332.         """Override in subclass to buffer data for send to client
  333.  
  334.         It's okay if this method actually transmits the data; BaseHandler
  335.         just separates write and flush operations for greater efficiency
  336.         when the underlying system actually has such a distinction.
  337.         """
  338.         raise NotImplementedError
  339.  
  340.     def _flush(self):
  341.         """Override in subclass to force sending of recent '_write()' calls
  342.  
  343.         It's okay if this method is a no-op (i.e., if '_write()' actually
  344.         sends the data.
  345.         """
  346.         raise NotImplementedError
  347.  
  348.     def get_stdin(self):
  349.         """Override in subclass to return suitable 'wsgi.input'"""
  350.         raise NotImplementedError
  351.  
  352.     def get_stderr(self):
  353.         """Override in subclass to return suitable 'wsgi.errors'"""
  354.         raise NotImplementedError
  355.  
  356.     def add_cgi_vars(self):
  357.         """Override in subclass to insert CGI variables in 'self.environ'"""
  358.         raise NotImplementedError
  359.  
  360.  
  361.  
  362.  
  363.  
  364.  
  365.  
  366.  
  367.  
  368.  
  369.  
  370. class SimpleHandler(BaseHandler):
  371.     """Handler that's just initialized with streams, environment, etc.
  372.  
  373.     This handler subclass is intended for synchronous HTTP/1.0 origin servers,
  374.     and handles sending the entire response output, given the correct inputs.
  375.  
  376.     Usage::
  377.  
  378.         handler = SimpleHandler(
  379.             inp,out,err,env, multithread=False, multiprocess=True
  380.         )
  381.         handler.run(app)"""
  382.  
  383.     def __init__(self,stdin,stdout,stderr,environ,
  384.         multithread=True, multiprocess=False
  385.     ):
  386.         self.stdin = stdin
  387.         self.stdout = stdout
  388.         self.stderr = stderr
  389.         self.base_env = environ
  390.         self.wsgi_multithread = multithread
  391.         self.wsgi_multiprocess = multiprocess
  392.  
  393.     def get_stdin(self):
  394.         return self.stdin
  395.  
  396.     def get_stderr(self):
  397.         return self.stderr
  398.  
  399.     def add_cgi_vars(self):
  400.         self.environ.update(self.base_env)
  401.  
  402.     def _write(self,data):
  403.         self.stdout.write(data)
  404.         self._write = self.stdout.write
  405.  
  406.     def _flush(self):
  407.         self.stdout.flush()
  408.         self._flush = self.stdout.flush
  409.  
  410.  
  411. class BaseCGIHandler(SimpleHandler):
  412.  
  413.     """CGI-like systems using input/output/error streams and environ mapping
  414.  
  415.     Usage::
  416.  
  417.         handler = BaseCGIHandler(inp,out,err,env)
  418.         handler.run(app)
  419.  
  420.     This handler class is useful for gateway protocols like ReadyExec and
  421.     FastCGI, that have usable input/output/error streams and an environment
  422.     mapping.  It's also the base class for CGIHandler, which just uses
  423.     sys.stdin, os.environ, and so on.
  424.  
  425.     The constructor also takes keyword arguments 'multithread' and
  426.     'multiprocess' (defaulting to 'True' and 'False' respectively) to control
  427.     the configuration sent to the application.  It sets 'origin_server' to
  428.     False (to enable CGI-like output), and assumes that 'wsgi.run_once' is
  429.     False.
  430.     """
  431.  
  432.     origin_server = False
  433.  
  434.  
  435.  
  436.  
  437.  
  438.  
  439.  
  440.  
  441.  
  442.  
  443.  
  444.  
  445.  
  446.  
  447.  
  448.  
  449.  
  450.  
  451.  
  452. class CGIHandler(BaseCGIHandler):
  453.  
  454.     """CGI-based invocation via sys.stdin/stdout/stderr and os.environ
  455.  
  456.     Usage::
  457.  
  458.         CGIHandler().run(app)
  459.  
  460.     The difference between this class and BaseCGIHandler is that it always
  461.     uses 'wsgi.run_once' of 'True', 'wsgi.multithread' of 'False', and
  462.     'wsgi.multiprocess' of 'True'.  It does not take any initialization
  463.     parameters, but always uses 'sys.stdin', 'os.environ', and friends.
  464.  
  465.     If you need to override any of these parameters, use BaseCGIHandler
  466.     instead.
  467.     """
  468.  
  469.     wsgi_run_once = True
  470.  
  471.     def __init__(self):
  472.         BaseCGIHandler.__init__(
  473.             self, sys.stdin, sys.stdout, sys.stderr, dict(os.environ.items()),
  474.             multithread=False, multiprocess=True
  475.         )
  476.  
  477.  
  478.  
  479.  
  480.  
  481.  
  482.  
  483.  
  484.  
  485.  
  486.  
  487.  
  488.  
  489.  
  490.  
  491.  
  492. #
  493.