pyramid: make responses/exceptions from pyramid/pylons work

This commit is contained in:
Daniel Dourvaris 2016-06-08 15:21:58 +03:00
parent d6e940710d
commit fdcf88bd53
6 changed files with 86 additions and 49 deletions

View file

@ -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=True, static_files=True, **app_conf):
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, global_config, settings):
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)
# 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)
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.
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_app, config):
"""
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'])

View file

@ -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

View file

@ -24,15 +24,21 @@ Disable VCS pages when VCS Server is not available
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)

View file

@ -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(handler, registry):
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.