##// END OF EJS Templates
pyramid: make responses/exceptions from pyramid/pylons work
dan -
r187:fc8c8497 default
parent child Browse files
Show More
@@ -8,7 +8,6 b''
8 8
9 9 [DEFAULT]
10 10 debug = true
11 pdebug = false
12 11 ################################################################################
13 12 ## Uncomment and replace with the email address which should receive ##
14 13 ## any error reports after an application crash ##
@@ -8,7 +8,6 b''
8 8
9 9 [DEFAULT]
10 10 debug = true
11 pdebug = false
12 11 ################################################################################
13 12 ## Uncomment and replace with the email address which should receive ##
14 13 ## any error reports after an application crash ##
@@ -25,13 +25,15 b' import logging'
25 25
26 26 from paste.registry import RegistryManager
27 27 from paste.gzipper import make_gzip_middleware
28 from pylons.middleware import ErrorHandler, StatusCodeRedirect
29 28 from pylons.wsgiapp import PylonsApp
30 29 from pyramid.authorization import ACLAuthorizationPolicy
31 30 from pyramid.config import Configurator
32 31 from pyramid.static import static_view
33 32 from pyramid.settings import asbool, aslist
34 33 from pyramid.wsgi import wsgiapp
34 from pyramid.httpexceptions import HTTPError
35 import pyramid.httpexceptions as httpexceptions
36 from pyramid.renderers import render_to_response
35 37 from routes.middleware import RoutesMiddleware
36 38 import routes.util
37 39
@@ -87,38 +89,15 b' def make_app(global_conf, full_stack=Tru'
87 89 app = csrf.OriginChecker(app, expected_origin,
88 90 skip_urls=[routes.util.url_for('api')])
89 91
90 # Add RoutesMiddleware. Currently we have two instances in the stack. This
91 # is the lower one to make the StatusCodeRedirect middleware happy.
92 # TODO: johbo: This is not optimal, search for a better solution.
93 app = RoutesMiddleware(app, config['routes.map'])
94
95 # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
96 if asbool(config['pdebug']):
97 from rhodecode.lib.profiler import ProfilingMiddleware
98 app = ProfilingMiddleware(app)
99
100 # Protect from VCS Server error related pages when server is not available
101 vcs_server_enabled = asbool(config.get('vcs.server.enable', 'true'))
102 if not vcs_server_enabled:
103 app = DisableVCSPagesWrapper(app)
104 92
105 93 if asbool(full_stack):
106 94
107 95 # Appenlight monitoring and error handler
108 96 app, appenlight_client = wrap_in_appenlight_if_enabled(app, config)
109 97
110 # Handle Python exceptions
111 app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
112
113 98 # we want our low level middleware to get to the request ASAP. We don't
114 99 # need any pylons stack middleware in them
115 100 app = VCSMiddleware(app, config, appenlight_client)
116 # Display error documents for 401, 403, 404 status codes (and
117 # 500 when debug is disabled)
118 if asbool(config['debug']):
119 app = StatusCodeRedirect(app)
120 else:
121 app = StatusCodeRedirect(app, [400, 401, 403, 404, 500])
122 101
123 102 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
124 103 app = HttpsFixup(app, config)
@@ -179,6 +158,36 b' def add_pylons_compat_data(registry, glo'
179 158 registry._pylons_compat_settings = settings
180 159
181 160
161 def error_handler(exc, request):
162 # TODO: dan: replace the old pylons error controller with this
163 from rhodecode.model.settings import SettingsModel
164 from rhodecode.lib.utils2 import AttributeDict
165
166 try:
167 rc_config = SettingsModel().get_all_settings()
168 except Exception:
169 log.exception('failed to fetch settings')
170 rc_config = {}
171
172 c = AttributeDict()
173 c.error_message = exc.status
174 c.error_explanation = exc.explanation or str(exc)
175 c.visual = AttributeDict()
176
177 c.visual.rhodecode_support_url = (
178 request.registry.settings.get('rhodecode_support_url') or
179 request.route_url('rhodecode_support')
180 )
181 c.redirect_time = 0
182 c.rhodecode_name = rc_config.get('rhodecode_title')
183 if not c.rhodecode_name:
184 c.rhodecode_name = 'Rhodecode'
185
186 response = render_to_response(
187 '/errors/error_document.html', {'c': c}, request=request)
188 return response
189
190
182 191 def includeme(config):
183 192 settings = config.registry.settings
184 193
@@ -189,6 +198,8 b' def includeme(config):'
189 198 config.include('rhodecode.login')
190 199 config.include('rhodecode.tweens')
191 200 config.include('rhodecode.api')
201 config.add_route(
202 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
192 203
193 204 # Set the authorization policy.
194 205 authz_policy = ACLAuthorizationPolicy()
@@ -207,17 +218,43 b' def includeme(config):'
207 218 for inc in includes:
208 219 config.include(inc)
209 220
221 pylons_app = make_app(
222 config.registry._pylons_compat_global_config,
223 **config.registry._pylons_compat_settings)
224 config.registry._pylons_compat_config = pylons_app.config
225
226 pylons_app_as_view = wsgiapp(pylons_app)
227
228 # Protect from VCS Server error related pages when server is not available
229 vcs_server_enabled = asbool(settings.get('vcs.server.enable', 'true'))
230 if not vcs_server_enabled:
231 pylons_app_as_view = DisableVCSPagesWrapper(pylons_app_as_view)
232
233
234 def pylons_app_with_error_handler(context, request):
235 """
236 Handle exceptions from rc pylons app:
237
238 - old webob type exceptions get converted to pyramid exceptions
239 - pyramid exceptions are passed to the error handler view
240 """
241 try:
242 response = pylons_app_as_view(context, request)
243 if 400 <= response.status_int <= 599: # webob type error responses
244 ExcClass = httpexceptions.status_map[response.status_int]
245 return error_handler(ExcClass(response.status), request)
246 except HTTPError as e: # pyramid type exceptions
247 return error_handler(e, request)
248
249 return response
250
210 251 # This is the glue which allows us to migrate in chunks. By registering the
211 252 # pylons based application as the "Not Found" view in Pyramid, we will
212 253 # fallback to the old application each time the new one does not yet know
213 254 # how to handle a request.
214 pylons_app = make_app(
215 config.registry._pylons_compat_global_config,
216 **config.registry._pylons_compat_settings)
217 config.registry._pylons_compat_config = pylons_app.config
218 pylons_app_as_view = wsgiapp(pylons_app)
219 config.add_notfound_view(pylons_app_as_view)
255 config.add_notfound_view(pylons_app_with_error_handler)
220 256
257 config.add_view(error_handler, context=HTTPError) # exceptions in rc pyramid
221 258
222 259 def includeme_last(config):
223 260 """
@@ -253,8 +290,7 b' def wrap_app_in_wsgi_middlewares(pyramid'
253 290 """
254 291 settings = config.registry.settings
255 292
256 # Add RoutesMiddleware. Currently we have two instances in the stack. This
257 # is the upper one to support the pylons compatibility tween during
293 # Add RoutesMiddleware to support the pylons compatibility tween during
258 294 # migration to pyramid.
259 295 pyramid_app = RoutesMiddleware(
260 296 pyramid_app, config.registry._pylons_compat_config['routes.map'])
@@ -166,10 +166,6 b' def make_map(config):'
166 166 def check_int(environ, match_dict):
167 167 return match_dict.get('id').isdigit()
168 168
169 # The ErrorController route (handles 404/500 error pages); it should
170 # likely stay at the top, ensuring it can always be resolved
171 rmap.connect('/error/{action}', controller='error')
172 rmap.connect('/error/{action}/{id}', controller='error')
173 169
174 170 #==========================================================================
175 171 # CUSTOM ROUTES HERE
@@ -24,15 +24,21 b' Disable VCS pages when VCS Server is not'
24 24
25 25 import logging
26 26 import re
27
27 from pyramid.httpexceptions import HTTPBadGateway
28 28
29 29 log = logging.getLogger(__name__)
30 30
31 31
32 class VCSServerUnavailable(HTTPBadGateway):
33 """ HTTP Exception class for when VCS Server is unavailable """
34 code = 502
35 title = 'VCS Server Required'
36 explanation = 'A VCS Server is required for this action. There is currently no VCS Server configured.'
37
32 38 class DisableVCSPagesWrapper(object):
33 39 """
34 Wrapper to disable all pages that require VCS Server to be running,
35 avoiding that errors explode to the user.
40 Pyramid view wrapper to disable all pages that require VCS Server to be
41 running, avoiding that errors explode to the user.
36 42
37 43 This Wrapper should be enabled only in case VCS Server is not available
38 44 for the instance.
@@ -60,11 +66,11 b' class DisableVCSPagesWrapper(object):'
60 66 log.debug('accessing: `%s` with VCS Server disabled', path_info)
61 67 return False
62 68
63 def __init__(self, app):
64 self.application = app
69 def __init__(self, handler):
70 self.handler = handler
65 71
66 def __call__(self, environ, start_response):
67 if not self._check_vcs_requirement(environ['PATH_INFO']):
68 environ['PATH_INFO'] = '/error/vcs_unavailable'
72 def __call__(self, context, request):
73 if not self._check_vcs_requirement(request.environ['PATH_INFO']):
74 raise VCSServerUnavailable('VCS Server is not available')
69 75
70 return self.application(environ, start_response)
76 return self.handler(context, request)
@@ -26,6 +26,7 b' import rhodecode'
26 26 from pylons.i18n.translation import _get_translator
27 27 from pylons.util import ContextObj
28 28 from routes.util import URLGenerator
29 from pyramid.httpexceptions import HTTPInternalServerError, HTTPError, HTTPServiceUnavailable
29 30
30 31 from rhodecode.lib.base import attach_context_attributes, get_auth_user
31 32 from rhodecode.model import meta
@@ -69,8 +70,8 b' def pylons_compatibility_tween_factory(h'
69 70 context.rhodecode_user = auth_user
70 71 attach_context_attributes(context)
71 72 pylons.tmpl_context._push_object(context)
72
73 return handler(request)
73 response = handler(request)
74 return response
74 75 finally:
75 76 # Dispose current database session and rollback uncommitted
76 77 # transactions.
General Comments 0
You need to be logged in to leave comments. Login now