wsgi/handler.py
changeset 0 b97547f5f1fa
child 1802 d628defebc17
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 """WSGI request handler for cubicweb
       
     2 
       
     3 :organization: Logilab
       
     4 :copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     6 """
       
     7 
       
     8 __docformat__ = "restructuredtext en"
       
     9 
       
    10 from cubicweb import ObjectNotFound, AuthenticationError
       
    11 from cubicweb.web import (NotFound, Redirect, DirectResponse, StatusResponse,
       
    12                        ExplicitLogin)
       
    13 from cubicweb.web.application import CubicWebPublisher
       
    14 from cubicweb.wsgi.request import CubicWebWsgiRequest
       
    15 
       
    16 # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
       
    17 STATUS_CODE_TEXT = {
       
    18     100: 'CONTINUE',
       
    19     101: 'SWITCHING PROTOCOLS',
       
    20     200: 'OK',
       
    21     201: 'CREATED',
       
    22     202: 'ACCEPTED',
       
    23     203: 'NON-AUTHORITATIVE INFORMATION',
       
    24     204: 'NO CONTENT',
       
    25     205: 'RESET CONTENT',
       
    26     206: 'PARTIAL CONTENT',
       
    27     300: 'MULTIPLE CHOICES',
       
    28     301: 'MOVED PERMANENTLY',
       
    29     302: 'FOUND',
       
    30     303: 'SEE OTHER',
       
    31     304: 'NOT MODIFIED',
       
    32     305: 'USE PROXY',
       
    33     306: 'RESERVED',
       
    34     307: 'TEMPORARY REDIRECT',
       
    35     400: 'BAD REQUEST',
       
    36     401: 'UNAUTHORIZED',
       
    37     402: 'PAYMENT REQUIRED',
       
    38     403: 'FORBIDDEN',
       
    39     404: 'NOT FOUND',
       
    40     405: 'METHOD NOT ALLOWED',
       
    41     406: 'NOT ACCEPTABLE',
       
    42     407: 'PROXY AUTHENTICATION REQUIRED',
       
    43     408: 'REQUEST TIMEOUT',
       
    44     409: 'CONFLICT',
       
    45     410: 'GONE',
       
    46     411: 'LENGTH REQUIRED',
       
    47     412: 'PRECONDITION FAILED',
       
    48     413: 'REQUEST ENTITY TOO LARGE',
       
    49     414: 'REQUEST-URI TOO LONG',
       
    50     415: 'UNSUPPORTED MEDIA TYPE',
       
    51     416: 'REQUESTED RANGE NOT SATISFIABLE',
       
    52     417: 'EXPECTATION FAILED',
       
    53     500: 'INTERNAL SERVER ERROR',
       
    54     501: 'NOT IMPLEMENTED',
       
    55     502: 'BAD GATEWAY',
       
    56     503: 'SERVICE UNAVAILABLE',
       
    57     504: 'GATEWAY TIMEOUT',
       
    58     505: 'HTTP VERSION NOT SUPPORTED',
       
    59 }
       
    60 
       
    61 
       
    62 class WSGIResponse(object):
       
    63     """encapsulates the wsgi response parameters
       
    64     (code, headers and body if there is one)
       
    65     """
       
    66     def __init__(self, code, req, body=None):
       
    67         text = STATUS_CODE_TEXT.get(code, 'UNKNOWN STATUS CODE')
       
    68         self.status =  '%s %s' % (code, text)
       
    69         self.headers = [(str(k), str(v)) for k, v in req.headers_out.items()]
       
    70         if body:
       
    71             self.body = [body]
       
    72         else:
       
    73             self.body = []
       
    74 
       
    75     def __iter__(self):
       
    76         return iter(self.body)
       
    77     
       
    78 
       
    79 
       
    80 class CubicWebWSGIApplication(object):
       
    81     """This is the wsgi application which will be called by the
       
    82     wsgi server with the WSGI ``environ`` and ``start_response``
       
    83     parameters.
       
    84 
       
    85     XXX: missing looping tasks and proper repository shutdown when
       
    86     the application is stopped.
       
    87     NOTE: no pyro
       
    88     """
       
    89 
       
    90     def __init__(self, config, debug=None, vreg=None):
       
    91         self.appli = CubicWebPublisher(config, debug=debug, vreg=vreg)
       
    92         self.debugmode = debug
       
    93         self.config = config
       
    94         self.base_url = None
       
    95 #         self.base_url = config['base-url'] or config.default_base_url()
       
    96 #         assert self.base_url[-1] == '/'
       
    97 #         self.https_url = config['https-url']
       
    98 #         assert not self.https_url or self.https_url[-1] == '/'
       
    99         try:
       
   100             self.url_rewriter = self.appli.vreg.select_component('urlrewriter')
       
   101         except ObjectNotFound:
       
   102             self.url_rewriter = None
       
   103         
       
   104     def _render(self, req):
       
   105         """this function performs the actual rendering
       
   106         XXX missing: https handling, url rewriting, cache management,
       
   107                      authentication
       
   108         """
       
   109         if self.base_url is None:
       
   110             self.base_url = self.config._base_url = req.base_url()
       
   111         # XXX https handling needs to be implemented
       
   112         if req.authmode == 'http':
       
   113             # activate realm-based auth
       
   114             realm = self.config['realm']
       
   115             req.set_header('WWW-Authenticate', [('Basic', {'realm' : realm })], raw=False)
       
   116         try:
       
   117             self.appli.connect(req)
       
   118         except AuthenticationError:
       
   119             return self.request_auth(req)
       
   120         except Redirect, ex:
       
   121             return self.redirect(req, ex.location)
       
   122         path = req.path
       
   123         if not path or path == "/":
       
   124             path = 'view'
       
   125         try:
       
   126             result = self.appli.publish(path, req)
       
   127         except DirectResponse, ex:
       
   128             return WSGIResponse(200, req, ex.response)
       
   129         except StatusResponse, ex:
       
   130             return WSGIResponse(ex.status, req, ex.content)
       
   131         except NotFound:
       
   132             result = self.appli.notfound_content(req)
       
   133             return WSGIResponse(404, req, result)
       
   134         except ExplicitLogin:  # must be before AuthenticationError
       
   135             return self.request_auth(req)
       
   136         except AuthenticationError:
       
   137             if self.config['auth-mode'] == 'cookie':
       
   138                 # in cookie mode redirecting to the index view is enough :
       
   139                 # either anonymous connection is allowed and the page will
       
   140                 # be displayed or we'll be redirected to the login form
       
   141                 msg = req._('you have been logged out')
       
   142 #                 if req.https:
       
   143 #                     req._base_url =  self.base_url
       
   144 #                     req.https = False
       
   145                 url = req.build_url('view', vid='index', __message=msg)
       
   146                 return self.redirect(req, url)
       
   147             else:
       
   148                 # in http we have to request auth to flush current http auth
       
   149                 # information
       
   150                 return self.request_auth(req, loggedout=True)
       
   151         except Redirect, ex:
       
   152             return self.redirect(req, ex.location)
       
   153         if not result:
       
   154             # no result, something went wrong...
       
   155             self.error('no data (%s)', req)
       
   156             # 500 Internal server error
       
   157             return self.redirect(req, req.build_url('error'))
       
   158         return WSGIResponse(200, req, result)
       
   159         
       
   160     
       
   161     def __call__(self, environ, start_response):
       
   162         """WSGI protocol entry point"""
       
   163         req = CubicWebWsgiRequest(environ, self.appli.vreg, self.base_url)
       
   164         response = self._render(req)
       
   165         start_response(response.status, response.headers)
       
   166         return response.body
       
   167 
       
   168     def redirect(self, req, location):
       
   169         """convenience function which builds a redirect WSGIResponse"""
       
   170         self.debug('redirecting to %s', location)
       
   171         req.set_header('location', str(location))
       
   172         return WSGIResponse(303, req)
       
   173         
       
   174     def request_auth(self, req, loggedout=False):
       
   175         """returns the appropriate WSGIResponse to require the user to log in
       
   176         """
       
   177 #         if self.https_url and req.base_url() != self.https_url:
       
   178 #             return self.redirect(self.https_url + 'login')
       
   179         if self.config['auth-mode'] == 'http':
       
   180             code = 401 # UNAUTHORIZED
       
   181         else:
       
   182             code = 403 # FORBIDDEN
       
   183         if loggedout:
       
   184 #             if req.https:
       
   185 #                 req._base_url =  self.base_url
       
   186 #                 req.https = False
       
   187             content = self.appli.loggedout_content(req)
       
   188         else:
       
   189             content = self.appli.need_login_content(req)
       
   190         return WSGIResponse(code, req, content)
       
   191 
       
   192 
       
   193 from logging import getLogger
       
   194 from cubicweb import set_log_methods
       
   195 set_log_methods(CubicWebWSGIApplication, getLogger('cubicweb.wsgi'))