##// 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 [DEFAULT]
9 [DEFAULT]
10 debug = true
10 debug = true
11 pdebug = false
12 ################################################################################
11 ################################################################################
13 ## Uncomment and replace with the email address which should receive ##
12 ## Uncomment and replace with the email address which should receive ##
14 ## any error reports after an application crash ##
13 ## any error reports after an application crash ##
@@ -8,7 +8,6 b''
8
8
9 [DEFAULT]
9 [DEFAULT]
10 debug = true
10 debug = true
11 pdebug = false
12 ################################################################################
11 ################################################################################
13 ## Uncomment and replace with the email address which should receive ##
12 ## Uncomment and replace with the email address which should receive ##
14 ## any error reports after an application crash ##
13 ## any error reports after an application crash ##
@@ -25,13 +25,15 b' import logging'
25
25
26 from paste.registry import RegistryManager
26 from paste.registry import RegistryManager
27 from paste.gzipper import make_gzip_middleware
27 from paste.gzipper import make_gzip_middleware
28 from pylons.middleware import ErrorHandler, StatusCodeRedirect
29 from pylons.wsgiapp import PylonsApp
28 from pylons.wsgiapp import PylonsApp
30 from pyramid.authorization import ACLAuthorizationPolicy
29 from pyramid.authorization import ACLAuthorizationPolicy
31 from pyramid.config import Configurator
30 from pyramid.config import Configurator
32 from pyramid.static import static_view
31 from pyramid.static import static_view
33 from pyramid.settings import asbool, aslist
32 from pyramid.settings import asbool, aslist
34 from pyramid.wsgi import wsgiapp
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 from routes.middleware import RoutesMiddleware
37 from routes.middleware import RoutesMiddleware
36 import routes.util
38 import routes.util
37
39
@@ -87,38 +89,15 b' def make_app(global_conf, full_stack=Tru'
87 app = csrf.OriginChecker(app, expected_origin,
89 app = csrf.OriginChecker(app, expected_origin,
88 skip_urls=[routes.util.url_for('api')])
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 if asbool(full_stack):
93 if asbool(full_stack):
106
94
107 # Appenlight monitoring and error handler
95 # Appenlight monitoring and error handler
108 app, appenlight_client = wrap_in_appenlight_if_enabled(app, config)
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 # we want our low level middleware to get to the request ASAP. We don't
98 # we want our low level middleware to get to the request ASAP. We don't
114 # need any pylons stack middleware in them
99 # need any pylons stack middleware in them
115 app = VCSMiddleware(app, config, appenlight_client)
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 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
102 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
124 app = HttpsFixup(app, config)
103 app = HttpsFixup(app, config)
@@ -179,6 +158,36 b' def add_pylons_compat_data(registry, glo'
179 registry._pylons_compat_settings = settings
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 def includeme(config):
191 def includeme(config):
183 settings = config.registry.settings
192 settings = config.registry.settings
184
193
@@ -189,6 +198,8 b' def includeme(config):'
189 config.include('rhodecode.login')
198 config.include('rhodecode.login')
190 config.include('rhodecode.tweens')
199 config.include('rhodecode.tweens')
191 config.include('rhodecode.api')
200 config.include('rhodecode.api')
201 config.add_route(
202 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
192
203
193 # Set the authorization policy.
204 # Set the authorization policy.
194 authz_policy = ACLAuthorizationPolicy()
205 authz_policy = ACLAuthorizationPolicy()
@@ -207,17 +218,43 b' def includeme(config):'
207 for inc in includes:
218 for inc in includes:
208 config.include(inc)
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 # This is the glue which allows us to migrate in chunks. By registering the
251 # This is the glue which allows us to migrate in chunks. By registering the
211 # pylons based application as the "Not Found" view in Pyramid, we will
252 # pylons based application as the "Not Found" view in Pyramid, we will
212 # fallback to the old application each time the new one does not yet know
253 # fallback to the old application each time the new one does not yet know
213 # how to handle a request.
254 # how to handle a request.
214 pylons_app = make_app(
255 config.add_notfound_view(pylons_app_with_error_handler)
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)
220
256
257 config.add_view(error_handler, context=HTTPError) # exceptions in rc pyramid
221
258
222 def includeme_last(config):
259 def includeme_last(config):
223 """
260 """
@@ -253,8 +290,7 b' def wrap_app_in_wsgi_middlewares(pyramid'
253 """
290 """
254 settings = config.registry.settings
291 settings = config.registry.settings
255
292
256 # Add RoutesMiddleware. Currently we have two instances in the stack. This
293 # Add RoutesMiddleware to support the pylons compatibility tween during
257 # is the upper one to support the pylons compatibility tween during
258 # migration to pyramid.
294 # migration to pyramid.
259 pyramid_app = RoutesMiddleware(
295 pyramid_app = RoutesMiddleware(
260 pyramid_app, config.registry._pylons_compat_config['routes.map'])
296 pyramid_app, config.registry._pylons_compat_config['routes.map'])
@@ -166,10 +166,6 b' def make_map(config):'
166 def check_int(environ, match_dict):
166 def check_int(environ, match_dict):
167 return match_dict.get('id').isdigit()
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 # CUSTOM ROUTES HERE
171 # CUSTOM ROUTES HERE
@@ -24,15 +24,21 b' Disable VCS pages when VCS Server is not'
24
24
25 import logging
25 import logging
26 import re
26 import re
27
27 from pyramid.httpexceptions import HTTPBadGateway
28
28
29 log = logging.getLogger(__name__)
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 class DisableVCSPagesWrapper(object):
38 class DisableVCSPagesWrapper(object):
33 """
39 """
34 Wrapper to disable all pages that require VCS Server to be running,
40 Pyramid view wrapper to disable all pages that require VCS Server to be
35 avoiding that errors explode to the user.
41 running, avoiding that errors explode to the user.
36
42
37 This Wrapper should be enabled only in case VCS Server is not available
43 This Wrapper should be enabled only in case VCS Server is not available
38 for the instance.
44 for the instance.
@@ -60,11 +66,11 b' class DisableVCSPagesWrapper(object):'
60 log.debug('accessing: `%s` with VCS Server disabled', path_info)
66 log.debug('accessing: `%s` with VCS Server disabled', path_info)
61 return False
67 return False
62
68
63 def __init__(self, app):
69 def __init__(self, handler):
64 self.application = app
70 self.handler = handler
65
71
66 def __call__(self, environ, start_response):
72 def __call__(self, context, request):
67 if not self._check_vcs_requirement(environ['PATH_INFO']):
73 if not self._check_vcs_requirement(request.environ['PATH_INFO']):
68 environ['PATH_INFO'] = '/error/vcs_unavailable'
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 from pylons.i18n.translation import _get_translator
26 from pylons.i18n.translation import _get_translator
27 from pylons.util import ContextObj
27 from pylons.util import ContextObj
28 from routes.util import URLGenerator
28 from routes.util import URLGenerator
29 from pyramid.httpexceptions import HTTPInternalServerError, HTTPError, HTTPServiceUnavailable
29
30
30 from rhodecode.lib.base import attach_context_attributes, get_auth_user
31 from rhodecode.lib.base import attach_context_attributes, get_auth_user
31 from rhodecode.model import meta
32 from rhodecode.model import meta
@@ -69,8 +70,8 b' def pylons_compatibility_tween_factory(h'
69 context.rhodecode_user = auth_user
70 context.rhodecode_user = auth_user
70 attach_context_attributes(context)
71 attach_context_attributes(context)
71 pylons.tmpl_context._push_object(context)
72 pylons.tmpl_context._push_object(context)
72
73 response = handler(request)
73 return handler(request)
74 return response
74 finally:
75 finally:
75 # Dispose current database session and rollback uncommitted
76 # Dispose current database session and rollback uncommitted
76 # transactions.
77 # transactions.
General Comments 0
You need to be logged in to leave comments. Login now