"Fossies" - the Fresh Open Source Software Archive

Member "PURELIB/trac/web/wsgi.py" (27 Aug 2019, 10115 Bytes) of package /windows/misc/Trac-1.4.win-amd64.exe:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "wsgi.py": 1.3.5_vs_1.3.6.

    1 # -*- coding: utf-8 -*-
    2 #
    3 # Copyright (C) 2005-2019 Edgewall Software
    4 # Copyright (C) 2005-2006 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 https://trac.edgewall.org/wiki/TracLicense.
   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 https://trac.edgewall.org/log/.
   14 #
   15 # Author: Christopher Lenz <cmlenz@gmx.de>
   16 
   17 from abc import ABCMeta, abstractmethod
   18 import errno
   19 import sys
   20 from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
   21 from SocketServer import ForkingMixIn, ThreadingMixIn
   22 import urllib
   23 
   24 
   25 # winsock errors
   26 _WSAECONNABORTED = 10053
   27 _WSAECONNRESET = 10054
   28 
   29 
   30 def is_client_disconnect_exception(e):
   31     """Determines whether the exception was caused by a disconnecting client.
   32 
   33     :type e: IOError
   34     :rtype: bool
   35     """
   36     return e.errno in (errno.EPIPE, errno.ECONNRESET, # Unix
   37                        _WSAECONNABORTED, _WSAECONNRESET, # Windows
   38                        None) # mod_wsgi, uwsgi, ... (see #12650)
   39 
   40 
   41 class _ErrorsWrapper(object):
   42 
   43     def __init__(self, logfunc):
   44         self.logfunc = logfunc
   45 
   46     def flush(self):
   47         pass
   48 
   49     def write(self, msg):
   50         self.logfunc(msg)
   51 
   52     def writelines(self, seq):
   53         map(self.write, seq)
   54 
   55 
   56 class _FileWrapper(object):
   57     """Wrapper for sending a file as response."""
   58 
   59     def __init__(self, fileobj, blocksize=None):
   60         self.fileobj = fileobj
   61         self.blocksize = blocksize
   62         self.read = self.fileobj.read
   63         if hasattr(fileobj, 'close'):
   64             self.close = fileobj.close
   65 
   66     def __iter__(self):
   67         return self
   68 
   69     def __next__(self):
   70         data = self.fileobj.read(self.blocksize)
   71         if not data:
   72             raise StopIteration
   73         return data
   74 
   75     next = __next__
   76 
   77 
   78 class WSGIGateway(object):
   79     """Abstract base class for WSGI servers or gateways."""
   80 
   81     __metaclass__ = ABCMeta
   82 
   83     wsgi_version = (1, 0)
   84     wsgi_multithread = True
   85     wsgi_multiprocess = True
   86     wsgi_run_once = False
   87     wsgi_file_wrapper = _FileWrapper
   88 
   89     def __init__(self, environ, stdin=sys.stdin, stderr=sys.stderr):
   90         """Initialize the gateway object."""
   91         environ['wsgi.version'] = self.wsgi_version
   92         environ['wsgi.url_scheme'] = 'http'
   93         if environ.get('HTTPS', '').lower() in ('yes', 'on', '1'):
   94             environ['wsgi.url_scheme'] = 'https'
   95         elif environ.get('HTTP_X_FORWARDED_PROTO', '').lower() == 'https':
   96             environ['wsgi.url_scheme'] = 'https'
   97         environ['wsgi.input'] = stdin
   98         environ['wsgi.errors'] = stderr
   99         environ['wsgi.multithread'] = self.wsgi_multithread
  100         environ['wsgi.multiprocess'] = self.wsgi_multiprocess
  101         environ['wsgi.run_once'] = self.wsgi_run_once
  102         if self.wsgi_file_wrapper is not None:
  103             environ['wsgi.file_wrapper'] = self.wsgi_file_wrapper
  104         self.environ = environ
  105 
  106         self.headers_set = []
  107         self.headers_sent = []
  108         self.use_chunked = False
  109 
  110     def run(self, application):
  111         """Start the gateway with the given WSGI application."""
  112         response = application(self.environ, self._start_response)
  113         try:
  114             if self.wsgi_file_wrapper is not None \
  115                     and isinstance(response, self.wsgi_file_wrapper) \
  116                     and hasattr(self, '_sendfile'):
  117                 self._sendfile(response.fileobj)
  118             else:
  119                 for chunk in response:
  120                     if chunk:
  121                         self._write(chunk)
  122                 if not self.headers_sent or self.use_chunked:
  123                     self._write('') # last chunk '\r\n0\r\n' if use_chunked
  124         finally:
  125             if hasattr(response, 'close'):
  126                 response.close()
  127 
  128     def _start_response(self, status, headers, exc_info=None):
  129         """Callback for starting a HTTP response."""
  130         if exc_info:
  131             try:
  132                 if self.headers_sent: # Re-raise original exception
  133                     raise exc_info[0], exc_info[1], exc_info[2]
  134             finally:
  135                 exc_info = None # avoid dangling circular ref
  136         else:
  137             assert not self.headers_set, 'Response already started'
  138 
  139         self.headers_set = [status, headers]
  140         return self._write
  141 
  142     @abstractmethod
  143     def _write(self, data):
  144         """Callback for writing data to the response.
  145 
  146         Concrete subclasses must implement this method."""
  147         pass
  148 
  149 
  150 class WSGIRequestHandler(BaseHTTPRequestHandler):
  151 
  152     def setup_environ(self):
  153         self.raw_requestline = self.rfile.readline()
  154         if (self.rfile.closed or              # disconnect
  155                 not self.raw_requestline or   # empty request
  156                 not self.parse_request()):    # invalid request
  157             self.close_connection = 1
  158             # note that in the latter case, an error code has already been sent
  159             return
  160 
  161         environ = self.server.environ.copy()
  162         environ['SERVER_PROTOCOL'] = self.request_version
  163         environ['REQUEST_METHOD'] = self.command
  164 
  165         if '?' in self.path:
  166             path_info, query_string = self.path.split('?', 1)
  167         else:
  168             path_info, query_string = self.path, ''
  169         environ['PATH_INFO'] = urllib.unquote(path_info)
  170         environ['QUERY_STRING'] = query_string
  171 
  172         host = self.address_string()
  173         if host != self.client_address[0]:
  174             environ['REMOTE_HOST'] = host
  175         environ['REMOTE_ADDR'] = self.client_address[0]
  176 
  177         if self.headers.typeheader is None:
  178             environ['CONTENT_TYPE'] = self.headers.type
  179         else:
  180             environ['CONTENT_TYPE'] = self.headers.typeheader
  181 
  182         length = self.headers.getheader('content-length')
  183         if length:
  184             environ['CONTENT_LENGTH'] = length
  185 
  186         for name, value in [header.split(':', 1) for header
  187                             in self.headers.headers]:
  188             name = name.replace('-', '_').upper()
  189             value = value.strip()
  190             if name in environ:
  191                 # skip content length, type, etc.
  192                 continue
  193             if 'HTTP_' + name in environ:
  194                 # comma-separate multiple headers
  195                 environ['HTTP_' + name] += ',' + value
  196             else:
  197                 environ['HTTP_' + name] = value
  198 
  199         return environ
  200 
  201     def handle_one_request(self):
  202         try:
  203             environ = self.setup_environ()
  204         except IOError as e:
  205             environ = None
  206             if is_client_disconnect_exception(e):
  207                 self.close_connection = 1
  208             else:
  209                 raise
  210         if environ:
  211             gateway = self.server.gateway(self, environ)
  212             gateway.run(self.server.application)
  213         # else we had no request or a bad request: we simply exit (#3043)
  214 
  215     def finish(self):
  216         """We need to help the garbage collector a little."""
  217         try:
  218             BaseHTTPRequestHandler.finish(self)
  219         except IOError as e:
  220             # ignore an exception if client disconnects
  221             if not is_client_disconnect_exception(e):
  222                 raise
  223         finally:
  224             self.wfile = None
  225             self.rfile = None
  226 
  227 
  228 class WSGIServerGateway(WSGIGateway):
  229 
  230     def __init__(self, handler, environ):
  231         WSGIGateway.__init__(self, environ, handler.rfile,
  232                              _ErrorsWrapper(lambda x: handler.log_error('%s', x)))
  233         self.handler = handler
  234 
  235     def _write(self, data):
  236         assert self.headers_set, 'Response not started'
  237         if self.handler.wfile.closed:
  238             return # don't write to an already closed file (fix for #1183)
  239 
  240         try:
  241             if not self.headers_sent:
  242                 # Worry at the last minute about Content-Length. If not
  243                 # yet set, use either chunked encoding or close connection
  244                 status, headers = self.headers_sent = self.headers_set
  245                 if any(n.lower() == 'content-length' for n, v in headers):
  246                     self.use_chunked = False
  247                 else:
  248                     self.use_chunked = (
  249                         self.environ['SERVER_PROTOCOL'] >= 'HTTP/1.1' and
  250                         self.handler.protocol_version >= 'HTTP/1.1')
  251                     if self.use_chunked:
  252                         headers.append(('Transfer-Encoding', 'chunked'))
  253                     else:
  254                         headers.append(('Connection', 'close'))
  255                 self.handler.send_response(int(status[:3]))
  256                 for name, value in headers:
  257                     self.handler.send_header(name, value)
  258                 self.handler.end_headers()
  259             if self.use_chunked:
  260                 self.handler.wfile.write('%x\r\n%s\r\n' % (len(data), data))
  261             else:
  262                 self.handler.wfile.write(data)
  263         except IOError as e:
  264             if is_client_disconnect_exception(e):
  265                 self.handler.close_connection = 1
  266             else:
  267                 raise
  268 
  269 
  270 class WSGIServer(HTTPServer):
  271 
  272     def __init__(self, server_address, application, gateway=WSGIServerGateway,
  273                  request_handler=WSGIRequestHandler):
  274         HTTPServer.__init__(self, server_address, request_handler)
  275 
  276         self.application = application
  277 
  278         gateway.wsgi_multithread = isinstance(self, ThreadingMixIn)
  279         gateway.wsgi_multiprocess = isinstance(self, ForkingMixIn)
  280         self.gateway = gateway
  281 
  282         self.environ = {'SERVER_NAME': self.server_name,
  283                         'SERVER_PORT': str(self.server_port),
  284                         'SCRIPT_NAME': ''}