# HG changeset patch # User Daniel Dourvaris # Date 2016-06-08 12:21:58 # Node ID fc8c849754a0ad8f0f848eace34058179820770d # Parent b33d2bf4eb719607d82540f1b2a6768c652d1cc4 pyramid: make responses/exceptions from pyramid/pylons work diff --git a/configs/development.ini b/configs/development.ini --- a/configs/development.ini +++ b/configs/development.ini @@ -8,7 +8,6 @@ [DEFAULT] debug = true -pdebug = false ################################################################################ ## Uncomment and replace with the email address which should receive ## ## any error reports after an application crash ## diff --git a/configs/production.ini b/configs/production.ini --- a/configs/production.ini +++ b/configs/production.ini @@ -8,7 +8,6 @@ [DEFAULT] debug = true -pdebug = false ################################################################################ ## Uncomment and replace with the email address which should receive ## ## any error reports after an application crash ## diff --git a/rhodecode/config/middleware.py b/rhodecode/config/middleware.py --- a/rhodecode/config/middleware.py +++ b/rhodecode/config/middleware.py @@ -25,13 +25,15 @@ import logging from paste.registry import RegistryManager from paste.gzipper import make_gzip_middleware -from pylons.middleware import ErrorHandler, StatusCodeRedirect from pylons.wsgiapp import PylonsApp from pyramid.authorization import ACLAuthorizationPolicy from pyramid.config import Configurator from pyramid.static import static_view from pyramid.settings import asbool, aslist from pyramid.wsgi import wsgiapp +from pyramid.httpexceptions import HTTPError +import pyramid.httpexceptions as httpexceptions +from pyramid.renderers import render_to_response from routes.middleware import RoutesMiddleware import routes.util @@ -87,38 +89,15 @@ def make_app(global_conf, full_stack=Tru app = csrf.OriginChecker(app, expected_origin, skip_urls=[routes.util.url_for('api')]) - # Add RoutesMiddleware. Currently we have two instances in the stack. This - # is the lower one to make the StatusCodeRedirect middleware happy. - # TODO: johbo: This is not optimal, search for a better solution. - app = RoutesMiddleware(app, config['routes.map']) - - # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares) - if asbool(config['pdebug']): - from rhodecode.lib.profiler import ProfilingMiddleware - app = ProfilingMiddleware(app) - - # Protect from VCS Server error related pages when server is not available - vcs_server_enabled = asbool(config.get('vcs.server.enable', 'true')) - if not vcs_server_enabled: - app = DisableVCSPagesWrapper(app) if asbool(full_stack): # Appenlight monitoring and error handler app, appenlight_client = wrap_in_appenlight_if_enabled(app, config) - # Handle Python exceptions - app = ErrorHandler(app, global_conf, **config['pylons.errorware']) - # we want our low level middleware to get to the request ASAP. We don't # need any pylons stack middleware in them app = VCSMiddleware(app, config, appenlight_client) - # Display error documents for 401, 403, 404 status codes (and - # 500 when debug is disabled) - if asbool(config['debug']): - app = StatusCodeRedirect(app) - else: - app = StatusCodeRedirect(app, [400, 401, 403, 404, 500]) # enable https redirects based on HTTP_X_URL_SCHEME set by proxy app = HttpsFixup(app, config) @@ -179,6 +158,36 @@ def add_pylons_compat_data(registry, glo registry._pylons_compat_settings = settings +def error_handler(exc, request): + # TODO: dan: replace the old pylons error controller with this + from rhodecode.model.settings import SettingsModel + from rhodecode.lib.utils2 import AttributeDict + + try: + rc_config = SettingsModel().get_all_settings() + except Exception: + log.exception('failed to fetch settings') + rc_config = {} + + c = AttributeDict() + c.error_message = exc.status + c.error_explanation = exc.explanation or str(exc) + c.visual = AttributeDict() + + c.visual.rhodecode_support_url = ( + request.registry.settings.get('rhodecode_support_url') or + request.route_url('rhodecode_support') + ) + c.redirect_time = 0 + c.rhodecode_name = rc_config.get('rhodecode_title') + if not c.rhodecode_name: + c.rhodecode_name = 'Rhodecode' + + response = render_to_response( + '/errors/error_document.html', {'c': c}, request=request) + return response + + def includeme(config): settings = config.registry.settings @@ -189,6 +198,8 @@ def includeme(config): config.include('rhodecode.login') config.include('rhodecode.tweens') config.include('rhodecode.api') + config.add_route( + 'rhodecode_support', 'https://rhodecode.com/help/', static=True) # Set the authorization policy. authz_policy = ACLAuthorizationPolicy() @@ -207,17 +218,43 @@ def includeme(config): for inc in includes: config.include(inc) + pylons_app = make_app( + config.registry._pylons_compat_global_config, + **config.registry._pylons_compat_settings) + config.registry._pylons_compat_config = pylons_app.config + + pylons_app_as_view = wsgiapp(pylons_app) + + # Protect from VCS Server error related pages when server is not available + vcs_server_enabled = asbool(settings.get('vcs.server.enable', 'true')) + if not vcs_server_enabled: + pylons_app_as_view = DisableVCSPagesWrapper(pylons_app_as_view) + + + def pylons_app_with_error_handler(context, request): + """ + Handle exceptions from rc pylons app: + + - old webob type exceptions get converted to pyramid exceptions + - pyramid exceptions are passed to the error handler view + """ + try: + response = pylons_app_as_view(context, request) + if 400 <= response.status_int <= 599: # webob type error responses + ExcClass = httpexceptions.status_map[response.status_int] + return error_handler(ExcClass(response.status), request) + except HTTPError as e: # pyramid type exceptions + return error_handler(e, request) + + return response + # This is the glue which allows us to migrate in chunks. By registering the # pylons based application as the "Not Found" view in Pyramid, we will # fallback to the old application each time the new one does not yet know # how to handle a request. - pylons_app = make_app( - config.registry._pylons_compat_global_config, - **config.registry._pylons_compat_settings) - config.registry._pylons_compat_config = pylons_app.config - pylons_app_as_view = wsgiapp(pylons_app) - config.add_notfound_view(pylons_app_as_view) + config.add_notfound_view(pylons_app_with_error_handler) + config.add_view(error_handler, context=HTTPError) # exceptions in rc pyramid def includeme_last(config): """ @@ -253,8 +290,7 @@ def wrap_app_in_wsgi_middlewares(pyramid """ settings = config.registry.settings - # Add RoutesMiddleware. Currently we have two instances in the stack. This - # is the upper one to support the pylons compatibility tween during + # Add RoutesMiddleware to support the pylons compatibility tween during # migration to pyramid. pyramid_app = RoutesMiddleware( pyramid_app, config.registry._pylons_compat_config['routes.map']) diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -166,10 +166,6 @@ def make_map(config): def check_int(environ, match_dict): return match_dict.get('id').isdigit() - # The ErrorController route (handles 404/500 error pages); it should - # likely stay at the top, ensuring it can always be resolved - rmap.connect('/error/{action}', controller='error') - rmap.connect('/error/{action}/{id}', controller='error') #========================================================================== # CUSTOM ROUTES HERE diff --git a/rhodecode/lib/middleware/disable_vcs.py b/rhodecode/lib/middleware/disable_vcs.py --- a/rhodecode/lib/middleware/disable_vcs.py +++ b/rhodecode/lib/middleware/disable_vcs.py @@ -24,15 +24,21 @@ Disable VCS pages when VCS Server is not import logging import re - +from pyramid.httpexceptions import HTTPBadGateway log = logging.getLogger(__name__) +class VCSServerUnavailable(HTTPBadGateway): + """ HTTP Exception class for when VCS Server is unavailable """ + code = 502 + title = 'VCS Server Required' + explanation = 'A VCS Server is required for this action. There is currently no VCS Server configured.' + class DisableVCSPagesWrapper(object): """ - Wrapper to disable all pages that require VCS Server to be running, - avoiding that errors explode to the user. + Pyramid view wrapper to disable all pages that require VCS Server to be + running, avoiding that errors explode to the user. This Wrapper should be enabled only in case VCS Server is not available for the instance. @@ -60,11 +66,11 @@ class DisableVCSPagesWrapper(object): log.debug('accessing: `%s` with VCS Server disabled', path_info) return False - def __init__(self, app): - self.application = app + def __init__(self, handler): + self.handler = handler - def __call__(self, environ, start_response): - if not self._check_vcs_requirement(environ['PATH_INFO']): - environ['PATH_INFO'] = '/error/vcs_unavailable' + def __call__(self, context, request): + if not self._check_vcs_requirement(request.environ['PATH_INFO']): + raise VCSServerUnavailable('VCS Server is not available') - return self.application(environ, start_response) + return self.handler(context, request) diff --git a/rhodecode/tweens.py b/rhodecode/tweens.py --- a/rhodecode/tweens.py +++ b/rhodecode/tweens.py @@ -26,6 +26,7 @@ import rhodecode from pylons.i18n.translation import _get_translator from pylons.util import ContextObj from routes.util import URLGenerator +from pyramid.httpexceptions import HTTPInternalServerError, HTTPError, HTTPServiceUnavailable from rhodecode.lib.base import attach_context_attributes, get_auth_user from rhodecode.model import meta @@ -69,8 +70,8 @@ def pylons_compatibility_tween_factory(h context.rhodecode_user = auth_user attach_context_attributes(context) pylons.tmpl_context._push_object(context) - - return handler(request) + response = handler(request) + return response finally: # Dispose current database session and rollback uncommitted # transactions.