wsgi/handler.py
changeset 0 b97547f5f1fa
child 1802 d628defebc17
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wsgi/handler.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,195 @@
+"""WSGI request handler for cubicweb
+
+:organization: Logilab
+:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+__docformat__ = "restructuredtext en"
+
+from cubicweb import ObjectNotFound, AuthenticationError
+from cubicweb.web import (NotFound, Redirect, DirectResponse, StatusResponse,
+                       ExplicitLogin)
+from cubicweb.web.application import CubicWebPublisher
+from cubicweb.wsgi.request import CubicWebWsgiRequest
+
+# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+STATUS_CODE_TEXT = {
+    100: 'CONTINUE',
+    101: 'SWITCHING PROTOCOLS',
+    200: 'OK',
+    201: 'CREATED',
+    202: 'ACCEPTED',
+    203: 'NON-AUTHORITATIVE INFORMATION',
+    204: 'NO CONTENT',
+    205: 'RESET CONTENT',
+    206: 'PARTIAL CONTENT',
+    300: 'MULTIPLE CHOICES',
+    301: 'MOVED PERMANENTLY',
+    302: 'FOUND',
+    303: 'SEE OTHER',
+    304: 'NOT MODIFIED',
+    305: 'USE PROXY',
+    306: 'RESERVED',
+    307: 'TEMPORARY REDIRECT',
+    400: 'BAD REQUEST',
+    401: 'UNAUTHORIZED',
+    402: 'PAYMENT REQUIRED',
+    403: 'FORBIDDEN',
+    404: 'NOT FOUND',
+    405: 'METHOD NOT ALLOWED',
+    406: 'NOT ACCEPTABLE',
+    407: 'PROXY AUTHENTICATION REQUIRED',
+    408: 'REQUEST TIMEOUT',
+    409: 'CONFLICT',
+    410: 'GONE',
+    411: 'LENGTH REQUIRED',
+    412: 'PRECONDITION FAILED',
+    413: 'REQUEST ENTITY TOO LARGE',
+    414: 'REQUEST-URI TOO LONG',
+    415: 'UNSUPPORTED MEDIA TYPE',
+    416: 'REQUESTED RANGE NOT SATISFIABLE',
+    417: 'EXPECTATION FAILED',
+    500: 'INTERNAL SERVER ERROR',
+    501: 'NOT IMPLEMENTED',
+    502: 'BAD GATEWAY',
+    503: 'SERVICE UNAVAILABLE',
+    504: 'GATEWAY TIMEOUT',
+    505: 'HTTP VERSION NOT SUPPORTED',
+}
+
+
+class WSGIResponse(object):
+    """encapsulates the wsgi response parameters
+    (code, headers and body if there is one)
+    """
+    def __init__(self, code, req, body=None):
+        text = STATUS_CODE_TEXT.get(code, 'UNKNOWN STATUS CODE')
+        self.status =  '%s %s' % (code, text)
+        self.headers = [(str(k), str(v)) for k, v in req.headers_out.items()]
+        if body:
+            self.body = [body]
+        else:
+            self.body = []
+
+    def __iter__(self):
+        return iter(self.body)
+    
+
+
+class CubicWebWSGIApplication(object):
+    """This is the wsgi application which will be called by the
+    wsgi server with the WSGI ``environ`` and ``start_response``
+    parameters.
+
+    XXX: missing looping tasks and proper repository shutdown when
+    the application is stopped.
+    NOTE: no pyro
+    """
+
+    def __init__(self, config, debug=None, vreg=None):
+        self.appli = CubicWebPublisher(config, debug=debug, vreg=vreg)
+        self.debugmode = debug
+        self.config = config
+        self.base_url = None
+#         self.base_url = config['base-url'] or config.default_base_url()
+#         assert self.base_url[-1] == '/'
+#         self.https_url = config['https-url']
+#         assert not self.https_url or self.https_url[-1] == '/'
+        try:
+            self.url_rewriter = self.appli.vreg.select_component('urlrewriter')
+        except ObjectNotFound:
+            self.url_rewriter = None
+        
+    def _render(self, req):
+        """this function performs the actual rendering
+        XXX missing: https handling, url rewriting, cache management,
+                     authentication
+        """
+        if self.base_url is None:
+            self.base_url = self.config._base_url = req.base_url()
+        # XXX https handling needs to be implemented
+        if req.authmode == 'http':
+            # activate realm-based auth
+            realm = self.config['realm']
+            req.set_header('WWW-Authenticate', [('Basic', {'realm' : realm })], raw=False)
+        try:
+            self.appli.connect(req)
+        except AuthenticationError:
+            return self.request_auth(req)
+        except Redirect, ex:
+            return self.redirect(req, ex.location)
+        path = req.path
+        if not path or path == "/":
+            path = 'view'
+        try:
+            result = self.appli.publish(path, req)
+        except DirectResponse, ex:
+            return WSGIResponse(200, req, ex.response)
+        except StatusResponse, ex:
+            return WSGIResponse(ex.status, req, ex.content)
+        except NotFound:
+            result = self.appli.notfound_content(req)
+            return WSGIResponse(404, req, result)
+        except ExplicitLogin:  # must be before AuthenticationError
+            return self.request_auth(req)
+        except AuthenticationError:
+            if self.config['auth-mode'] == 'cookie':
+                # in cookie mode redirecting to the index view is enough :
+                # either anonymous connection is allowed and the page will
+                # be displayed or we'll be redirected to the login form
+                msg = req._('you have been logged out')
+#                 if req.https:
+#                     req._base_url =  self.base_url
+#                     req.https = False
+                url = req.build_url('view', vid='index', __message=msg)
+                return self.redirect(req, url)
+            else:
+                # in http we have to request auth to flush current http auth
+                # information
+                return self.request_auth(req, loggedout=True)
+        except Redirect, ex:
+            return self.redirect(req, ex.location)
+        if not result:
+            # no result, something went wrong...
+            self.error('no data (%s)', req)
+            # 500 Internal server error
+            return self.redirect(req, req.build_url('error'))
+        return WSGIResponse(200, req, result)
+        
+    
+    def __call__(self, environ, start_response):
+        """WSGI protocol entry point"""
+        req = CubicWebWsgiRequest(environ, self.appli.vreg, self.base_url)
+        response = self._render(req)
+        start_response(response.status, response.headers)
+        return response.body
+
+    def redirect(self, req, location):
+        """convenience function which builds a redirect WSGIResponse"""
+        self.debug('redirecting to %s', location)
+        req.set_header('location', str(location))
+        return WSGIResponse(303, req)
+        
+    def request_auth(self, req, loggedout=False):
+        """returns the appropriate WSGIResponse to require the user to log in
+        """
+#         if self.https_url and req.base_url() != self.https_url:
+#             return self.redirect(self.https_url + 'login')
+        if self.config['auth-mode'] == 'http':
+            code = 401 # UNAUTHORIZED
+        else:
+            code = 403 # FORBIDDEN
+        if loggedout:
+#             if req.https:
+#                 req._base_url =  self.base_url
+#                 req.https = False
+            content = self.appli.loggedout_content(req)
+        else:
+            content = self.appli.need_login_content(req)
+        return WSGIResponse(code, req, content)
+
+
+from logging import getLogger
+from cubicweb import set_log_methods
+set_log_methods(CubicWebWSGIApplication, getLogger('cubicweb.wsgi'))