# HG changeset patch # User Pierre-Yves David # Date 1331830100 -3600 # Node ID 6c2119509fac4712513610ba36e5fdcb2157a6e8 # Parent 76a44a0d7f4b4784d0b82108e1256b1040eb7f23 [web] Move request handling logic into cubicweb application. (closes #2200684) We improve http status handling in the process: ``application.publish`` have been renamed to ``application.handle`` to better reflect it's roles. The request object gain a status_out attribute to convey the HTTP status of the response. WSGI and etwist code have been updated. Exception gain status attribute diff -r 76a44a0d7f4b -r 6c2119509fac devtools/testlib.py --- a/devtools/testlib.py Thu Mar 15 17:42:31 2012 +0100 +++ b/devtools/testlib.py Thu Mar 15 17:48:20 2012 +0100 @@ -608,8 +608,8 @@ ctrl = self.vreg['controllers'].select('ajax', req) return ctrl.publish(), req - def app_publish(self, req, path='view'): - return self.app.publish(path, req) + def app_handle_request(self, req, path='view'): + return self.app.core_handle(req, path) def ctrl_publish(self, req, ctrl='edit'): """call the publish method of the edit controller""" @@ -646,6 +646,20 @@ ctrlid, rset = self.app.url_resolver.process(req, req.relative_path(False)) return self.ctrl_publish(req, ctrlid) + @staticmethod + def _parse_location(req, location): + try: + path, params = location.split('?', 1) + except ValueError: + path = location + params = {} + else: + cleanup = lambda p: (p[0], unquote(p[1])) + params = dict(cleanup(p.split('=', 1)) for p in params.split('&') if p) + if path.startswith(req.base_url()): # may be relative + path = path[len(req.base_url()):] + return path, params + def expect_redirect(self, callback, req): """call the given callback with req as argument, expecting to get a Redirect exception @@ -653,25 +667,18 @@ try: callback(req) except Redirect, ex: - try: - path, params = ex.location.split('?', 1) - except ValueError: - path = ex.location - params = {} - else: - cleanup = lambda p: (p[0], unquote(p[1])) - params = dict(cleanup(p.split('=', 1)) for p in params.split('&') if p) - if path.startswith(req.base_url()): # may be relative - path = path[len(req.base_url()):] - return path, params + return self._parse_location(req, ex.location) else: self.fail('expected a Redirect exception') - def expect_redirect_publish(self, req, path='edit'): + def expect_redirect_handle_request(self, req, path='edit'): """call the publish method of the application publisher, expecting to get a Redirect exception """ - return self.expect_redirect(lambda x: self.app_publish(x, path), req) + result = self.app_handle_request(req, path) + self.assertTrue(300 <= req.status_out <400, req.status_out) + location = req.get_response_header('location') + return self._parse_location(req, location) def set_auth_mode(self, authmode, anonuser=None): self.set_option('auth-mode', authmode) diff -r 76a44a0d7f4b -r 6c2119509fac doc/book/en/devweb/request.rst --- a/doc/book/en/devweb/request.rst Thu Mar 15 17:42:31 2012 +0100 +++ b/doc/book/en/devweb/request.rst Thu Mar 15 17:48:20 2012 +0100 @@ -99,6 +99,7 @@ document.ready(...) or another ajax-friendly one-time trigger event * `add_header(header, values)`: adds the header/value pair to the current html headers + * `status_out`: control the HTTP status of the response * `And more...` diff -r 76a44a0d7f4b -r 6c2119509fac etwist/server.py --- a/etwist/server.py Thu Mar 15 17:42:31 2012 +0100 +++ b/etwist/server.py Thu Mar 15 17:48:20 2012 +0100 @@ -162,75 +162,23 @@ origpath = origpath[6:] request.uri = request.uri[6:] https = True - req = CubicWebTwistedRequestAdapter(request, self.appli.vreg, https) - 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 Redirect, ex: - return self.redirect(request=req, location=ex.location) - if https and req.session.anonymous_session and self.config['https-deny-anonymous']: - # don't allow anonymous on https connection - return self.request_auth(request=req) if self.url_rewriter is not None: # XXX should occur before authentication? - try: - path = self.url_rewriter.rewrite(host, origpath, req) - except Redirect, ex: - return self.redirect(req, ex.location) + path = self.url_rewriter.rewrite(host, origpath, request) request.uri.replace(origpath, path, 1) else: path = origpath + req = CubicWebTwistedRequestAdapter(request, self.appli.vreg, https) try: - result = self.appli.publish(path, req) + ### Try to generate the actual request content + content = self.appli.handle_request(req, path) except DirectResponse, ex: return ex.response - except StatusResponse, ex: - return HTTPResponse(stream=ex.content, code=ex.status, - twisted_request=req._twreq, - headers=req.headers_out) - except AuthenticationError: - return self.request_auth(request=req) - except LogOut, ex: - if self.config['auth-mode'] == 'cookie' and ex.url: - return self.redirect(request=req, location=ex.url) - # in http we have to request auth to flush current http auth - # information - return self.request_auth(request=req, loggedout=True) - except Redirect, ex: - return self.redirect(request=req, location=ex.location) - # request may be referenced by "onetime callback", so clear its entity - # cache to avoid memory usage - req.drop_entity_cache() - return HTTPResponse(twisted_request=req._twreq, code=http.OK, - stream=result, headers=req.headers_out) - - def redirect(self, request, location): - self.debug('redirecting to %s', str(location)) - request.headers_out.setHeader('location', str(location)) - # 303 See other - return HTTPResponse(twisted_request=request._twreq, code=303, - headers=request.headers_out) - - def request_auth(self, request, loggedout=False): - if self.https_url and request.base_url() != self.https_url: - return self.redirect(request, self.https_url + 'login') - if self.config['auth-mode'] == 'http': - code = http.UNAUTHORIZED - else: - code = http.FORBIDDEN - if loggedout: - if request.https: - request._base_url = self.base_url - request.https = False - content = self.appli.loggedout_content(request) - else: - content = self.appli.need_login_content(request) - return HTTPResponse(twisted_request=request._twreq, - stream=content, code=code, - headers=request.headers_out) + # at last: create twisted object + return HTTPResponse(code = req.status_out, + headers = req.headers_out, + stream = content, + twisted_request=req._twreq) # these are overridden by set_log_methods below # only defining here to prevent pylint from complaining diff -r 76a44a0d7f4b -r 6c2119509fac web/_exceptions.py --- a/web/_exceptions.py Thu Mar 15 17:42:31 2012 +0100 +++ b/web/_exceptions.py Thu Mar 15 17:48:20 2012 +0100 @@ -20,59 +20,90 @@ __docformat__ = "restructuredtext en" +import httplib + from cubicweb._exceptions import * from cubicweb.utils import json_dumps + +class DirectResponse(Exception): + """Used to supply a twitted HTTP Response directly""" + def __init__(self, response): + self.response = response + +class InvalidSession(CubicWebException): + """raised when a session id is found but associated session is not found or + invalid""" + +# Publish related exception + class PublishException(CubicWebException): """base class for publishing related exception""" + def __init__(self, *args, **kwargs): + self.status = kwargs.pop('status', httplib.OK) + super(PublishException, self).__init__(*args, **kwargs) + +class LogOut(PublishException): + """raised to ask for deauthentication of a logged in user""" + def __init__(self, url=None): + super(LogOut, self).__init__() + self.url = url + +class Redirect(PublishException): + """raised to redirect the http request""" + def __init__(self, location, status=httplib.SEE_OTHER): + super(Redirect, self).__init__(status=status) + self.location = location + +class StatusResponse(PublishException): + + def __init__(self, status, content=''): + super(StatusResponse, self).__init__(status=status) + self.content = content + + def __repr__(self): + return '%s(%r, %r)' % (self.__class__.__name__, self.status, self.content) + self.url = url + +# Publish related error + class RequestError(PublishException): """raised when a request can't be served because of a bad input""" + def __init__(self, *args, **kwargs): + kwargs.setdefault('status', httplib.BAD_REQUEST) + super(RequestError, self).__init__(*args, **kwargs) + + class NothingToEdit(RequestError): """raised when an edit request doesn't specify any eid to edit""" + def __init__(self, *args, **kwargs): + kwargs.setdefault('status', httplib.BAD_REQUEST) + super(NothingToEdit, self).__init__(*args, **kwargs) + class ProcessFormError(RequestError): """raised when posted data can't be processed by the corresponding field """ + def __init__(self, *args, **kwargs): + kwargs.setdefault('status', httplib.BAD_REQUEST) + super(ProcessFormError, self).__init__(*args, **kwargs) class NotFound(RequestError): - """raised when a 404 error should be returned""" - -class Redirect(PublishException): - """raised to redirect the http request""" - def __init__(self, location): - self.location = location - -class DirectResponse(Exception): - def __init__(self, response): - self.response = response + """raised when something was not found. In most case, + a 404 error should be returned""" -class StatusResponse(Exception): - def __init__(self, status, content=''): - self.status = int(status) - self.content = content - - def __repr__(self): - return '%s(%r, %r)' % (self.__class__.__name__, self.status, self.content) - -class InvalidSession(CubicWebException): - """raised when a session id is found but associated session is not found or - invalid - """ + def __init__(self, *args, **kwargs): + kwargs.setdefault('status', httplib.NOT_FOUND) + super(NotFound, self).__init__(*args, **kwargs) class RemoteCallFailed(RequestError): """raised when a json remote call fails """ - def __init__(self, reason=''): - super(RemoteCallFailed, self).__init__() + def __init__(self, reason='', status=httplib.INTERNAL_SERVER_ERROR): + super(RemoteCallFailed, self).__init__(status=status) self.reason = reason def dumps(self): return json_dumps({'reason': self.reason}) - -class LogOut(PublishException): - """raised to ask for deauthentication of a logged in user""" - def __init__(self, url): - super(LogOut, self).__init__() - self.url = url diff -r 76a44a0d7f4b -r 6c2119509fac web/application.py --- a/web/application.py Thu Mar 15 17:42:31 2012 +0100 +++ b/web/application.py Thu Mar 15 17:48:20 2012 +0100 @@ -24,6 +24,9 @@ import sys from time import clock, time from contextlib import contextmanager +from warnings import warn + +import httplib from logilab.common.deprecation import deprecated @@ -39,6 +42,8 @@ StatusResponse, DirectResponse, Redirect, NotFound, LogOut, RemoteCallFailed, InvalidSession, RequestError) +from cubicweb.web.request import CubicWebRequestBase + # make session manager available through a global variable so the debug view can # print information about web session SESSION_MANAGER = None @@ -288,11 +293,11 @@ if config['query-log-file']: from threading import Lock self._query_log = open(config['query-log-file'], 'a') - self.publish = self.log_publish + self.handle_request = self.log_handle_request self._logfile_lock = Lock() else: self._query_log = None - self.publish = self.main_publish + self.handle_request = self.main_handle_request # instantiate session and url resolving helpers self.session_handler = session_handler_fact(self) self.set_urlresolver() @@ -311,12 +316,12 @@ # publish methods ######################################################### - def log_publish(self, path, req): + def log_handle_request(self, req, path): """wrapper around _publish to log all queries executed for a given accessed path """ try: - return self.main_publish(path, req) + return self.main_handle_request(req, path) finally: cnx = req.cnx if cnx: @@ -332,7 +337,78 @@ except Exception: self.exception('error while logging queries') - def main_publish(self, path, req): + + + def main_handle_request(self, req, path): + if not isinstance(req, CubicWebRequestBase): + warn('[3.15] Application entry poin arguments are now (req, path) ' + 'not (path, req)', DeprecationWarning, 2) + req, path = path, req + if req.authmode == 'http': + # activate realm-based auth + realm = self.vreg.config['realm'] + req.set_header('WWW-Authenticate', [('Basic', {'realm' : realm })], raw=False) + try: + self.connect(req) + # DENY https acces for anonymous_user + if (req.https + and req.session.anonymous_session + and self.vreg.config['https-deny-anonymous']): + # don't allow anonymous on https connection + raise AuthenticationError() + content = '' + # nested try to allow LogOut to delegate logic to AuthenticationError + # handler + try: + ### Try to generate the actual request content + content = self.core_handle(req, path) + # Handle user log-out + except LogOut, ex: + # When authentification is handled by cookie the code that + # raised LogOut must has invalidated the cookie. We can just + # reload the original url without authentification + if self.vreg.config['auth-mode'] == 'cookie' and ex.url: + req.headers_out.setHeader('location', str(ex.url)) + if ex.status is not None: + req.status_out = httplib.SEE_OTHER + # When the authentification is handled by http we must + # explicitly ask for authentification to flush current http + # authentification information + else: + # Render "logged out" content. + # assignement to ``content`` prevent standard + # AuthenticationError code to overwrite it. + content = self.loggedout_content(req) + # let the explicitly reset http credential + raise AuthenticationError() + # Wrong, absent or Reseted credential + except AuthenticationError: + # If there is an https url configured and + # the request do not used https, redirect to login form + https_url = self.vreg.config['https-url'] + if https_url and req.base_url() != https_url: + req.status_out = httplib.SEE_OTHER + req.headers_out.setHeader('location', https_url + 'login') + else: + # We assume here that in http auth mode the user *May* provide + # Authentification Credential if asked kindly. + if self.vreg.config['auth-mode'] == 'http': + req.status_out = httplib.UNAUTHORIZED + # In the other case (coky auth) we assume that there is no way + # for the user to provide them... + # XXX But WHY ? + else: + req.status_out = httplib.FORBIDDEN + # If previous error handling already generated a custom content + # do not overwrite it. This is used by LogOut Except + # XXX ensure we don't actually serve content + if not content: + content = self.need_login_content(req) + return content + + + + def core_handle(self, req, path): """method called by the main publisher to process should return a string containing the resulting page or raise a @@ -355,6 +431,7 @@ tstart = clock() commited = False try: + ### standard processing of the request try: ctrlid, rset = self.url_resolver.process(req, path) try: @@ -364,76 +441,69 @@ raise Unauthorized(req._('not authorized')) req.update_search_state() result = controller.publish(rset=rset) - if req.cnx: - # no req.cnx if anonymous aren't allowed and we are - # displaying some anonymous enabled view such as the cookie - # authentication form - txuuid = req.cnx.commit() - if txuuid is not None: - req.data['last_undoable_transaction'] = txuuid - commited = True - except (StatusResponse, DirectResponse): - if req.cnx: - req.cnx.commit() - raise - except (AuthenticationError, LogOut): - raise - except Redirect: - # redirect is raised by edit controller when everything went fine, - # so try to commit - try: - if req.cnx: - txuuid = req.cnx.commit() - if txuuid is not None: - req.data['last_undoable_transaction'] = txuuid - except ValidationError, ex: - self.validation_error_handler(req, ex) - except Unauthorized, ex: - req.data['errmsg'] = req._('You\'re not authorized to access this page. ' - 'If you think you should, please contact the site administrator.') - self.error_handler(req, ex, tb=False) - except Exception, ex: - self.error_handler(req, ex, tb=True) - else: - self.add_undo_link_to_msg(req) - # delete validation errors which may have been previously set - if '__errorurl' in req.form: - req.session.data.pop(req.form['__errorurl'], None) - raise - except RemoteCallFailed, ex: - req.set_header('content-type', 'application/json') - raise StatusResponse(500, ex.dumps()) - except NotFound: - raise StatusResponse(404, self.notfound_content(req)) - except ValidationError, ex: - self.validation_error_handler(req, ex) - except Unauthorized, ex: - self.error_handler(req, ex, tb=False, code=403) - except (BadRQLQuery, RequestError), ex: - self.error_handler(req, ex, tb=False) - except BaseException, ex: - self.error_handler(req, ex, tb=True) - except: - self.critical('Catch all triggered!!!') - self.exception('this is what happened') - result = 'oops' + except StatusResponse, ex: + warn('StatusResponse is deprecated use req.status_out', + DeprecationWarning) + result = ex.content + req.status_out = ex.status + except Redirect, ex: + # handle redirect + # - comply to ex status + # - set header field + # + # Redirect Maybe be is raised by edit controller when + # everything went fine, so try to commit + self.debug('redirecting to %s', str(ex.location)) + req.headers_out.setHeader('location', str(ex.location)) + assert 300<= ex.status < 400 + req.status_out = ex.status + result = '' + if req.cnx: + txuuid = req.cnx.commit() + commited = True + if txuuid is not None: + req.data['last_undoable_transaction'] = txuuid + ### error case + except NotFound, ex: + result = self.notfound_content(req) + req.status_out = ex.status + except ValidationError, ex: + req.status_out = httplib.CONFLICT + result = self.validation_error_handler(req, ex) + except RemoteCallFailed, ex: + result = self.ajax_error_handler(req, ex) + except Unauthorized, ex: + req.data['errmsg'] = req._('You\'re not authorized to access this page. ' + 'If you think you should, please contact the site administrator.') + req.status_out = httplib.UNAUTHORIZED + result = self.error_handler(req, ex, tb=False) + except (BadRQLQuery, RequestError), ex: + result = self.error_handler(req, ex, tb=False) + ### pass through exception + except DirectResponse: + if req.cnx: + req.cnx.commit() + raise + except (AuthenticationError, LogOut): + # the rollback is handled in the finally + raise + ### Last defence line + except BaseException, ex: + result = self.error_handler(req, ex, tb=True) finally: if req.cnx and not commited: try: req.cnx.rollback() except Exception: pass # ignore rollback error at this point + # request may be referenced by "onetime callback", so clear its entity + # cache to avoid memory usage + req.drop_entity_cache() self.add_undo_link_to_msg(req) self.info('query %s executed in %s sec', req.relative_path(), clock() - tstart) return result - def add_undo_link_to_msg(self, req): - txuuid = req.data.get('last_undoable_transaction') - if txuuid is not None: - msg = u'[%s]' %( - req.build_url('undo', txuuid=txuuid), req._('undo')) - req.append_to_redirect_message(msg) - + ### Error handler def validation_error_handler(self, req, ex): ex.errors = dict((k, v) for k, v in ex.errors.items()) if '__errorurl' in req.form: @@ -446,10 +516,13 @@ # session key is 'url + #
[%s]' %( + req.build_url('undo', txuuid=txuuid), req._('undo')) + req.append_to_redirect_message(msg) + + + + def ajax_error_handler(self, req, ex): + req.set_header('content-type', 'application/json') + status = ex.status + if status is None: + status = httplib.INTERNAL_SERVER_ERROR + json_dumper = getattr(ex, 'dumps', lambda : unicode(ex)) + req.status_out = status + return json_dumper() + + # special case handling def need_login_content(self, req): return self.vreg['views'].main_template(req, 'login') @@ -482,6 +577,8 @@ template = self.main_template_id(req) return self.vreg['views'].main_template(req, template, view=view) + # template stuff + def main_template_id(self, req): template = req.form.get('__template', req.property_value('ui.main-template')) if template not in self.vreg['views']: diff -r 76a44a0d7f4b -r 6c2119509fac web/request.py --- a/web/request.py Thu Mar 15 17:42:31 2012 +0100 +++ b/web/request.py Thu Mar 15 17:48:20 2012 +0100 @@ -122,6 +122,8 @@ # prepare output header #: Header used for the final response self.headers_out = Headers() + #: HTTP status use by the final response + self.status_out = 200 def _set_pageid(self): """initialize self.pageid diff -r 76a44a0d7f4b -r 6c2119509fac web/test/data/views.py --- a/web/test/data/views.py Thu Mar 15 17:42:31 2012 +0100 +++ b/web/test/data/views.py Thu Mar 15 17:48:20 2012 +0100 @@ -21,12 +21,12 @@ from cubicweb.web import Redirect from cubicweb.web.application import CubicWebPublisher -# proof of concept : monkey patch publish method so that if we are in an +# proof of concept : monkey patch handle method so that if we are in an # anonymous session and __fblogin is found is req.form, the user with the # given login is created if necessary and then a session is opened for that # user # NOTE: this require "cookie" authentication mode -def auto_login_publish(self, path, req): +def auto_login_handle_request(self, req, path): if (not req.cnx or req.cnx.anonymous_connection) and req.form.get('__fblogin'): login = password = req.form.pop('__fblogin') self.repo.register_user(login, password) @@ -40,7 +40,7 @@ except Redirect: pass assert req.user.login == login - return orig_publish(self, path, req) + return orig_handle(self, req, path) -orig_publish = CubicWebPublisher.main_publish -CubicWebPublisher.main_publish = auto_login_publish +orig_handle = CubicWebPublisher.main_handle_request +CubicWebPublisher.main_handle_request = auto_login_handle_request diff -r 76a44a0d7f4b -r 6c2119509fac web/test/unittest_application.py --- a/web/test/unittest_application.py Thu Mar 15 17:42:31 2012 +0100 +++ b/web/test/unittest_application.py Thu Mar 15 17:48:20 2012 +0100 @@ -184,12 +184,12 @@ def test_nonregr_publish1(self): req = self.request(u'CWEType X WHERE X final FALSE, X meta FALSE') - self.app.publish('view', req) + self.app.handle_request(req, 'view') def test_nonregr_publish2(self): req = self.request(u'Any count(N) WHERE N todo_by U, N is Note, U eid %s' % self.user().eid) - self.app.publish('view', req) + self.app.handle_request(req, 'view') def test_publish_validation_error(self): req = self.request() @@ -202,7 +202,7 @@ # just a sample, missing some necessary information for real life '__errorurl': 'view?vid=edition...' } - path, params = self.expect_redirect(lambda x: self.app_publish(x, 'edit'), req) + path, params = self.expect_redirect_handle_request(req, 'edit') forminfo = req.session.data['view?vid=edition...'] eidmap = forminfo['eidmap'] self.assertEqual(eidmap, {}) @@ -232,7 +232,7 @@ # necessary to get validation error handling '__errorurl': 'view?vid=edition...', } - path, params = self.expect_redirect(lambda x: self.app_publish(x, 'edit'), req) + path, params = self.expect_redirect_handle_request(req, 'edit') forminfo = req.session.data['view?vid=edition...'] self.assertEqual(set(forminfo['eidmap']), set('XY')) self.assertEqual(forminfo['eidmap']['X'], None) @@ -261,7 +261,7 @@ # necessary to get validation error handling '__errorurl': 'view?vid=edition...', } - path, params = self.expect_redirect(lambda x: self.app_publish(x, 'edit'), req) + path, params = self.expect_redirect_handle_request(req, 'edit') forminfo = req.session.data['view?vid=edition...'] self.assertEqual(set(forminfo['eidmap']), set('XY')) self.assertIsInstance(forminfo['eidmap']['X'], int) @@ -274,7 +274,7 @@ def _test_cleaned(self, kwargs, injected, cleaned): req = self.request(**kwargs) - page = self.app.publish('view', req) + page = self.app.handle_request(req, 'view') self.assertFalse(injected in page, (kwargs, injected)) self.assertTrue(cleaned in page, (kwargs, cleaned)) @@ -315,7 +315,7 @@ req = self.request() origcnx = req.cnx req.form['__fblogin'] = u'turlututu' - page = self.app_publish(req) + page = self.app.handle_request(req, '') self.assertFalse(req.cnx is origcnx) self.assertEqual(req.user.login, 'turlututu') self.assertTrue('turlututu' in page, page) @@ -326,19 +326,19 @@ def test_http_auth_no_anon(self): req, origsession = self.init_authentication('http') self.assertAuthFailure(req) - self.assertRaises(AuthenticationError, self.app_publish, req, 'login') + self.assertRaises(AuthenticationError, self.app_handle_request, req, 'login') self.assertEqual(req.cnx, None) authstr = base64.encodestring('%s:%s' % (self.admlogin, self.admpassword)) req.set_request_header('Authorization', 'basic %s' % authstr) self.assertAuthSuccess(req, origsession) - self.assertRaises(LogOut, self.app_publish, req, 'logout') + self.assertRaises(LogOut, self.app_handle_request, req, 'logout') self.assertEqual(len(self.open_sessions), 0) def test_cookie_auth_no_anon(self): req, origsession = self.init_authentication('cookie') self.assertAuthFailure(req) try: - form = self.app_publish(req, 'login') + form = self.app_handle_request(req, 'login') except Redirect, redir: self.fail('anonymous user should get login form') self.assertTrue('__login' in form) @@ -347,7 +347,7 @@ req.form['__login'] = self.admlogin req.form['__password'] = self.admpassword self.assertAuthSuccess(req, origsession) - self.assertRaises(LogOut, self.app_publish, req, 'logout') + self.assertRaises(LogOut, self.app_handle_request, req, 'logout') self.assertEqual(len(self.open_sessions), 0) def test_login_by_email(self): @@ -367,7 +367,7 @@ req.form['__login'] = address req.form['__password'] = self.admpassword self.assertAuthSuccess(req, origsession) - self.assertRaises(LogOut, self.app_publish, req, 'logout') + self.assertRaises(LogOut, self.app_handle_request, req, 'logout') self.assertEqual(len(self.open_sessions), 0) def _reset_cookie(self, req): @@ -407,7 +407,7 @@ authstr = base64.encodestring('%s:%s' % (self.admlogin, self.admpassword)) req.set_request_header('Authorization', 'basic %s' % authstr) self.assertAuthSuccess(req, origsession) - self.assertRaises(LogOut, self.app_publish, req, 'logout') + self.assertRaises(LogOut, self.app_handle_request, req, 'logout') self.assertEqual(len(self.open_sessions), 0) def test_cookie_auth_anon_allowed(self): @@ -419,7 +419,7 @@ req.form['__login'] = self.admlogin req.form['__password'] = self.admpassword self.assertAuthSuccess(req, origsession) - self.assertRaises(LogOut, self.app_publish, req, 'logout') + self.assertRaises(LogOut, self.app_handle_request, req, 'logout') self.assertEqual(len(self.open_sessions), 0) def test_anonymized_request(self): @@ -438,7 +438,7 @@ req = self.request() # expect a rset with None in [0][0] req.form['rql'] = 'rql:Any OV1, X WHERE X custom_workflow OV1?' - self.app_publish(req) + self.app_handle_request(req) if __name__ == '__main__': unittest_main() diff -r 76a44a0d7f4b -r 6c2119509fac web/test/unittest_views_basecontrollers.py --- a/web/test/unittest_views_basecontrollers.py Thu Mar 15 17:42:31 2012 +0100 +++ b/web/test/unittest_views_basecontrollers.py Thu Mar 15 17:48:20 2012 +0100 @@ -96,7 +96,7 @@ 'firstname-subject:'+eid: u'Sylvain', 'in_group-subject:'+eid: groups, } - path, params = self.expect_redirect_publish(req, 'edit') + path, params = self.expect_redirect_handle_request(req, 'edit') e = self.execute('Any X WHERE X eid %(x)s', {'x': user.eid}).get_entity(0, 0) self.assertEqual(e.firstname, u'Sylvain') self.assertEqual(e.surname, u'Th\xe9nault') @@ -115,7 +115,7 @@ 'upassword-subject:'+eid: 'tournicoton', 'upassword-subject-confirm:'+eid: 'tournicoton', } - path, params = self.expect_redirect_publish(req, 'edit') + path, params = self.expect_redirect_handle_request(req, 'edit') cnx.commit() # commit to check we don't get late validation error for instance self.assertEqual(path, 'cwuser/user') self.assertFalse('vid' in params) @@ -136,7 +136,7 @@ 'firstname-subject:'+eid: u'Th\xe9nault', 'surname-subject:'+eid: u'Sylvain', } - path, params = self.expect_redirect_publish(req, 'edit') + path, params = self.expect_redirect_handle_request(req, 'edit') e = self.execute('Any X WHERE X eid %(x)s', {'x': user.eid}).get_entity(0, 0) self.assertEqual(e.login, user.login) self.assertEqual(e.firstname, u'Th\xe9nault') @@ -162,7 +162,7 @@ 'address-subject:Y': u'dima@logilab.fr', 'use_email-object:Y': 'X', } - path, params = self.expect_redirect_publish(req, 'edit') + path, params = self.expect_redirect_handle_request(req, 'edit') # should be redirected on the created person self.assertEqual(path, 'cwuser/adim') e = self.execute('Any P WHERE P surname "Di Mascio"').get_entity(0, 0) @@ -184,7 +184,7 @@ 'address-subject:Y': u'dima@logilab.fr', 'use_email-object:Y': peid, } - path, params = self.expect_redirect_publish(req, 'edit') + path, params = self.expect_redirect_handle_request(req, 'edit') # should be redirected on the created person self.assertEqual(path, 'cwuser/adim') e = self.execute('Any P WHERE P surname "Di Masci"').get_entity(0, 0) @@ -204,7 +204,7 @@ 'address-subject:'+emaileid: u'adim@logilab.fr', 'use_email-object:'+emaileid: peid, } - path, params = self.expect_redirect_publish(req, 'edit') + path, params = self.expect_redirect_handle_request(req, 'edit') email.cw_clear_all_caches() self.assertEqual(email.address, 'adim@logilab.fr') @@ -267,7 +267,7 @@ 'amount-subject:X': u'10', 'described_by_test-subject:X': u(feid), } - self.expect_redirect_publish(req, 'edit') + self.expect_redirect_handle_request(req, 'edit') # should be redirected on the created #eid = params['rql'].split()[-1] e = self.execute('Salesterm X').get_entity(0, 0) @@ -279,7 +279,7 @@ user = self.user() req = self.request(**req_form(user)) req.session.data['pending_insert'] = set([(user.eid, 'in_group', tmpgroup.eid)]) - path, params = self.expect_redirect_publish(req, 'edit') + path, params = self.expect_redirect_handle_request(req, 'edit') usergroups = [gname for gname, in self.execute('Any N WHERE G name N, U in_group G, U eid %(u)s', {'u': user.eid})] self.assertItemsEqual(usergroups, ['managers', 'test']) @@ -298,7 +298,7 @@ # now try to delete the relation req = self.request(**req_form(user)) req.session.data['pending_delete'] = set([(user.eid, 'in_group', groupeid)]) - path, params = self.expect_redirect_publish(req, 'edit') + path, params = self.expect_redirect_handle_request(req, 'edit') usergroups = [gname for gname, in self.execute('Any N WHERE G name N, U in_group G, U eid %(u)s', {'u': user.eid})] self.assertItemsEqual(usergroups, ['managers']) @@ -318,7 +318,7 @@ '__form_id': 'edition', '__action_apply': '', } - path, params = self.expect_redirect_publish(req, 'edit') + path, params = self.expect_redirect_handle_request(req, 'edit') self.assertTrue(path.startswith('blogentry/')) eid = path.split('/')[1] self.assertEqual(params['vid'], 'edition') @@ -340,7 +340,7 @@ '__redirectparams': 'toto=tutu&tata=titi', '__form_id': 'edition', } - path, params = self.expect_redirect_publish(req, 'edit') + path, params = self.expect_redirect_handle_request(req, 'edit') self.assertEqual(path, 'view') self.assertEqual(params['rql'], redirectrql) self.assertEqual(params['vid'], 'primary') @@ -352,7 +352,7 @@ eid = req.create_entity('BlogEntry', title=u'hop', content=u'hop').eid req.form = {'eid': u(eid), '__type:%s'%eid: 'BlogEntry', '__action_delete': ''} - path, params = self.expect_redirect_publish(req, 'edit') + path, params = self.expect_redirect_handle_request(req, 'edit') self.assertEqual(path, 'blogentry') self.assertIn('_cwmsgid', params) eid = req.create_entity('EmailAddress', address=u'hop@logilab.fr').eid @@ -362,7 +362,7 @@ req = req req.form = {'eid': u(eid), '__type:%s'%eid: 'EmailAddress', '__action_delete': ''} - path, params = self.expect_redirect_publish(req, 'edit') + path, params = self.expect_redirect_handle_request(req, 'edit') self.assertEqual(path, 'cwuser/admin') self.assertIn('_cwmsgid', params) eid1 = req.create_entity('BlogEntry', title=u'hop', content=u'hop').eid @@ -372,7 +372,7 @@ '__type:%s'%eid1: 'BlogEntry', '__type:%s'%eid2: 'EmailAddress', '__action_delete': ''} - path, params = self.expect_redirect_publish(req, 'edit') + path, params = self.expect_redirect_handle_request(req, 'edit') self.assertEqual(path, 'view') self.assertIn('_cwmsgid', params) @@ -388,7 +388,7 @@ 'title-subject:X': u'entry1-copy', 'content-subject:X': u'content1', } - self.expect_redirect_publish(req, 'edit') + self.expect_redirect_handle_request(req, 'edit') blogentry2 = req.find_one_entity('BlogEntry', title=u'entry1-copy') self.assertEqual(blogentry2.entry_of[0].eid, blog.eid) @@ -406,7 +406,7 @@ 'title-subject:X': u'entry1-copy', 'content-subject:X': u'content1', } - self.expect_redirect_publish(req, 'edit') + self.expect_redirect_handle_request(req, 'edit') blogentry2 = req.find_one_entity('BlogEntry', title=u'entry1-copy') # entry_of should not be copied self.assertEqual(len(blogentry2.entry_of), 0) @@ -432,7 +432,7 @@ 'read_permission-subject:'+cwetypeeid: groups, } try: - path, params = self.expect_redirect_publish(req, 'edit') + path, params = self.expect_redirect_handle_request(req, 'edit') e = self.execute('Any X WHERE X eid %(x)s', {'x': cwetypeeid}).get_entity(0, 0) self.assertEqual(e.name, 'CWEType') self.assertEqual(sorted(g.eid for g in e.read_permission), groupeids) @@ -452,7 +452,7 @@ '__type:A': 'BlogEntry', '_cw_entity_fields:A': 'title-subject,content-subject', 'title-subject:A': u'"13:03:40"', 'content-subject:A': u'"13:03:43"',} - path, params = self.expect_redirect_publish(req, 'edit') + path, params = self.expect_redirect_handle_request(req, 'edit') self.assertTrue(path.startswith('blogentry/')) eid = path.split('/')[1] e = self.execute('Any C, T WHERE C eid %(x)s, C content T', {'x': eid}).get_entity(0, 0) @@ -490,7 +490,7 @@ 'login-subject:X': u'toto', 'upassword-subject:X': u'toto', 'upassword-subject-confirm:X': u'toto', } - path, params = self.expect_redirect_publish(req, 'edit') + path, params = self.expect_redirect_handle_request(req, 'edit') self.assertEqual(path, 'cwuser/toto') e = self.execute('Any X WHERE X is CWUser, X login "toto"').get_entity(0, 0) self.assertEqual(e.login, 'toto') @@ -520,12 +520,12 @@ # which fires a Redirect # 2/ When re-publishing the copy form, the publisher implicitly commits try: - self.app_publish(req, 'edit') + self.app_handle_request(req, 'edit') except Redirect: req = self.request() req.form['rql'] = 'Any X WHERE X eid %s' % p.eid req.form['vid'] = 'copy' - self.app_publish(req, 'view') + self.app_handle_request(req, 'view') rset = self.execute('CWUser P WHERE P surname "Boom"') self.assertEqual(len(rset), 0) finally: diff -r 76a44a0d7f4b -r 6c2119509fac web/test/unittest_views_staticcontrollers.py --- a/web/test/unittest_views_staticcontrollers.py Thu Mar 15 17:42:31 2012 +0100 +++ b/web/test/unittest_views_staticcontrollers.py Thu Mar 15 17:48:20 2012 +0100 @@ -26,7 +26,7 @@ head = HTMLHead(req) url = head.concat_urls([req.data_url(js_file) for js_file in js_files])[len(req.base_url()):] req._url = url - return self.app_publish(req, url) + return self.app_handle_request(req, url), req def expected_content(self, js_files): content = u'' @@ -39,13 +39,8 @@ def test_cache(self): js_files = ('cubicweb.ajax.js', 'jquery.js') - try: - result = self._publish_js_files(js_files) - except StatusResponse, exc: - if exc.status == 404: - self.fail('unable to serve cubicweb.js+jquery.js') - # let the exception propagate for any other status (e.g 500) - raise + result, req = self._publish_js_files(js_files) + self.assertNotEqual(404, req.status_out) # check result content self.assertEqual(result, self.expected_content(js_files)) # make sure we kept a cached version on filesystem @@ -59,23 +54,16 @@ # in debug mode, an error is raised self.config.debugmode = True try: - result = self._publish_js_files(js_files) - self.fail('invalid concat js should return a 404 in debug mode') - except StatusResponse, exc: - if exc.status != 404: - self.fail('invalid concat js should return a 404 in debug mode') + result, req = self._publish_js_files(js_files) + #print result + self.assertEqual(404, req.status_out) finally: self.config.debugmode = False def test_invalid_file_in_production_mode(self): js_files = ('cubicweb.ajax.js', 'dummy.js') - try: - result = self._publish_js_files(js_files) - except StatusResponse, exc: - if exc.status == 404: - self.fail('invalid concat js should NOT return a 404 in debug mode') - # let the exception propagate for any other status (e.g 500) - raise + result, req = self._publish_js_files(js_files) + self.assertNotEqual(404, req.status_out) # check result content self.assertEqual(result, self.expected_content(js_files)) diff -r 76a44a0d7f4b -r 6c2119509fac wsgi/handler.py --- a/wsgi/handler.py Thu Mar 15 17:42:31 2012 +0100 +++ b/wsgi/handler.py Thu Mar 15 17:48:20 2012 +0100 @@ -112,51 +112,15 @@ 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 Redirect, ex: - return self.redirect(req, ex.location) - try: - result = self.appli.publish(path, req) + path = req.path + result = self.appli.handle_request(req, path) except DirectResponse, ex: - return WSGIResponse(200, req, ex.response) - except StatusResponse, ex: - return WSGIResponse(ex.status, req, ex.content) - except AuthenticationError: # must be before AuthenticationError - return self.request_auth(req) - except LogOut: - 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) + return ex.response + return WSGIResponse(req.status_out, req, result) def __call__(self, environ, start_response): @@ -166,29 +130,7 @@ 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) # these are overridden by set_log_methods below # only defining here to prevent pylint from complaining