##// END OF EJS Templates
errorpages: use original http status for rendered error page
dan -
r190:299c0020 default
parent child Browse files
Show More
@@ -1,351 +1,358 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Pylons middleware initialization
23 23 """
24 24 import logging
25 25
26 26 from paste.registry import RegistryManager
27 27 from paste.gzipper import make_gzip_middleware
28 28 from pylons.wsgiapp import PylonsApp
29 29 from pyramid.authorization import ACLAuthorizationPolicy
30 30 from pyramid.config import Configurator
31 31 from pyramid.static import static_view
32 32 from pyramid.settings import asbool, aslist
33 33 from pyramid.wsgi import wsgiapp
34 from pyramid.httpexceptions import HTTPError
34 from pyramid.httpexceptions import HTTPError, HTTPInternalServerError
35 35 import pyramid.httpexceptions as httpexceptions
36 from pyramid.renderers import render_to_response
36 from pyramid.renderers import render_to_response, render
37 37 from routes.middleware import RoutesMiddleware
38 38 import routes.util
39 39
40 40 import rhodecode
41 41 from rhodecode.config import patches
42 42 from rhodecode.config.environment import (
43 43 load_environment, load_pyramid_environment)
44 44 from rhodecode.lib.middleware import csrf
45 45 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
46 46 from rhodecode.lib.middleware.disable_vcs import DisableVCSPagesWrapper
47 47 from rhodecode.lib.middleware.https_fixup import HttpsFixup
48 48 from rhodecode.lib.middleware.vcs import VCSMiddleware
49 49 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
50 50
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54
55 55 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
56 56 """Create a Pylons WSGI application and return it
57 57
58 58 ``global_conf``
59 59 The inherited configuration for this application. Normally from
60 60 the [DEFAULT] section of the Paste ini file.
61 61
62 62 ``full_stack``
63 63 Whether or not this application provides a full WSGI stack (by
64 64 default, meaning it handles its own exceptions and errors).
65 65 Disable full_stack when this application is "managed" by
66 66 another WSGI middleware.
67 67
68 68 ``app_conf``
69 69 The application's local configuration. Normally specified in
70 70 the [app:<name>] section of the Paste ini file (where <name>
71 71 defaults to main).
72 72
73 73 """
74 74 # Apply compatibility patches
75 75 patches.kombu_1_5_1_python_2_7_11()
76 76 patches.inspect_getargspec()
77 77
78 78 # Configure the Pylons environment
79 79 config = load_environment(global_conf, app_conf)
80 80
81 81 # The Pylons WSGI app
82 82 app = PylonsApp(config=config)
83 83 if rhodecode.is_test:
84 84 app = csrf.CSRFDetector(app)
85 85
86 86 expected_origin = config.get('expected_origin')
87 87 if expected_origin:
88 88 # The API can be accessed from other Origins.
89 89 app = csrf.OriginChecker(app, expected_origin,
90 90 skip_urls=[routes.util.url_for('api')])
91 91
92 92
93 93 if asbool(full_stack):
94 94
95 95 # Appenlight monitoring and error handler
96 96 app, appenlight_client = wrap_in_appenlight_if_enabled(app, config)
97 97
98 98 # we want our low level middleware to get to the request ASAP. We don't
99 99 # need any pylons stack middleware in them
100 100 app = VCSMiddleware(app, config, appenlight_client)
101 101
102 102 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
103 103 app = HttpsFixup(app, config)
104 104
105 105 # Establish the Registry for this application
106 106 app = RegistryManager(app)
107 107
108 108 app.config = config
109 109
110 110 return app
111 111
112 112
113 113 def make_pyramid_app(global_config, **settings):
114 114 """
115 115 Constructs the WSGI application based on Pyramid and wraps the Pylons based
116 116 application.
117 117
118 118 Specials:
119 119
120 120 * We migrate from Pylons to Pyramid. While doing this, we keep both
121 121 frameworks functional. This involves moving some WSGI middlewares around
122 122 and providing access to some data internals, so that the old code is
123 123 still functional.
124 124
125 125 * The application can also be integrated like a plugin via the call to
126 126 `includeme`. This is accompanied with the other utility functions which
127 127 are called. Changing this should be done with great care to not break
128 128 cases when these fragments are assembled from another place.
129 129
130 130 """
131 131 # The edition string should be available in pylons too, so we add it here
132 132 # before copying the settings.
133 133 settings.setdefault('rhodecode.edition', 'Community Edition')
134 134
135 135 # As long as our Pylons application does expect "unprepared" settings, make
136 136 # sure that we keep an unmodified copy. This avoids unintentional change of
137 137 # behavior in the old application.
138 138 settings_pylons = settings.copy()
139 139
140 140 sanitize_settings_and_apply_defaults(settings)
141 141 config = Configurator(settings=settings)
142 142 add_pylons_compat_data(config.registry, global_config, settings_pylons)
143 143
144 144 load_pyramid_environment(global_config, settings)
145 145
146 146 includeme(config)
147 147 includeme_last(config)
148 148 pyramid_app = config.make_wsgi_app()
149 149 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
150 150 return pyramid_app
151 151
152 152
153 153 def add_pylons_compat_data(registry, global_config, settings):
154 154 """
155 155 Attach data to the registry to support the Pylons integration.
156 156 """
157 157 registry._pylons_compat_global_config = global_config
158 158 registry._pylons_compat_settings = settings
159 159
160 160
161 161 def error_handler(exc, request):
162 162 # TODO: dan: replace the old pylons error controller with this
163 163 from rhodecode.model.settings import SettingsModel
164 164 from rhodecode.lib.utils2 import AttributeDict
165 165
166 166 try:
167 167 rc_config = SettingsModel().get_all_settings()
168 168 except Exception:
169 169 log.exception('failed to fetch settings')
170 170 rc_config = {}
171 171
172 172 c = AttributeDict()
173 173 c.error_message = exc.status
174 174 c.error_explanation = exc.explanation or str(exc)
175 175 c.visual = AttributeDict()
176 176
177 177 c.visual.rhodecode_support_url = (
178 178 request.registry.settings.get('rhodecode_support_url') or
179 179 request.route_url('rhodecode_support')
180 180 )
181 181 c.redirect_time = 0
182 182 c.rhodecode_name = rc_config.get('rhodecode_title')
183 183 if not c.rhodecode_name:
184 184 c.rhodecode_name = 'Rhodecode'
185 185
186 base_response = HTTPInternalServerError()
187 # prefer original exception for the response since it may have headers set
188 if isinstance(exc, HTTPError):
189 base_response = exc
190
186 191 response = render_to_response(
187 '/errors/error_document.html', {'c': c}, request=request)
192 '/errors/error_document.html', {'c': c}, request=request,
193 response=base_response)
194
188 195 return response
189 196
190 197
191 198 def includeme(config):
192 199 settings = config.registry.settings
193 200
194 201 # Includes which are required. The application would fail without them.
195 202 config.include('pyramid_mako')
196 203 config.include('pyramid_beaker')
197 204 config.include('rhodecode.authentication')
198 205 config.include('rhodecode.login')
199 206 config.include('rhodecode.tweens')
200 207 config.include('rhodecode.api')
201 208 config.add_route(
202 209 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
203 210
204 211 # Set the authorization policy.
205 212 authz_policy = ACLAuthorizationPolicy()
206 213 config.set_authorization_policy(authz_policy)
207 214
208 215 # Set the default renderer for HTML templates to mako.
209 216 config.add_mako_renderer('.html')
210 217
211 218 # plugin information
212 219 config.registry.rhodecode_plugins = {}
213 220
214 221 config.add_directive(
215 222 'register_rhodecode_plugin', register_rhodecode_plugin)
216 223 # include RhodeCode plugins
217 224 includes = aslist(settings.get('rhodecode.includes', []))
218 225 for inc in includes:
219 226 config.include(inc)
220 227
221 228 pylons_app = make_app(
222 229 config.registry._pylons_compat_global_config,
223 230 **config.registry._pylons_compat_settings)
224 231 config.registry._pylons_compat_config = pylons_app.config
225 232
226 233 pylons_app_as_view = wsgiapp(pylons_app)
227 234
228 235 # Protect from VCS Server error related pages when server is not available
229 236 vcs_server_enabled = asbool(settings.get('vcs.server.enable', 'true'))
230 237 if not vcs_server_enabled:
231 238 pylons_app_as_view = DisableVCSPagesWrapper(pylons_app_as_view)
232 239
233 240
234 241 def pylons_app_with_error_handler(context, request):
235 242 """
236 243 Handle exceptions from rc pylons app:
237 244
238 245 - old webob type exceptions get converted to pyramid exceptions
239 246 - pyramid exceptions are passed to the error handler view
240 247 """
241 248 try:
242 249 response = pylons_app_as_view(context, request)
243 250 if 400 <= response.status_int <= 599: # webob type error responses
244 251 ExcClass = httpexceptions.status_map[response.status_int]
245 252 return error_handler(ExcClass(response.status), request)
246 253 except HTTPError as e: # pyramid type exceptions
247 254 return error_handler(e, request)
248 255
249 256 return response
250 257
251 258 # This is the glue which allows us to migrate in chunks. By registering the
252 259 # pylons based application as the "Not Found" view in Pyramid, we will
253 260 # fallback to the old application each time the new one does not yet know
254 261 # how to handle a request.
255 262 config.add_notfound_view(pylons_app_with_error_handler)
256 263
257 264 config.add_view(error_handler, context=HTTPError) # exceptions in rc pyramid
258 265
259 266 def includeme_last(config):
260 267 """
261 268 The static file catchall needs to be last in the view configuration.
262 269 """
263 270 settings = config.registry.settings
264 271
265 272 # Note: johbo: I would prefer to register a prefix for static files at some
266 273 # point, e.g. move them under '_static/'. This would fully avoid that we
267 274 # can have name clashes with a repository name. Imaging someone calling his
268 275 # repo "css" ;-) Also having an external web server to serve out the static
269 276 # files seems to be easier to set up if they have a common prefix.
270 277 #
271 278 # Example: config.add_static_view('_static', path='rhodecode:public')
272 279 #
273 280 # It might be an option to register both paths for a while and then migrate
274 281 # over to the new location.
275 282
276 283 # Serving static files with a catchall.
277 284 if settings['static_files']:
278 285 config.add_route('catchall_static', '/*subpath')
279 286 config.add_view(
280 287 static_view('rhodecode:public'), route_name='catchall_static')
281 288
282 289
283 290 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
284 291 """
285 292 Apply outer WSGI middlewares around the application.
286 293
287 294 Part of this has been moved up from the Pylons layer, so that the
288 295 data is also available if old Pylons code is hit through an already ported
289 296 view.
290 297 """
291 298 settings = config.registry.settings
292 299
293 300 # Add RoutesMiddleware to support the pylons compatibility tween during
294 301 # migration to pyramid.
295 302 pyramid_app = RoutesMiddleware(
296 303 pyramid_app, config.registry._pylons_compat_config['routes.map'])
297 304
298 305 # TODO: johbo: Don't really see why we enable the gzip middleware when
299 306 # serving static files, might be something that should have its own setting
300 307 # as well?
301 308 if settings['static_files']:
302 309 pyramid_app = make_gzip_middleware(
303 310 pyramid_app, settings, compress_level=1)
304 311
305 312 return pyramid_app
306 313
307 314
308 315 def sanitize_settings_and_apply_defaults(settings):
309 316 """
310 317 Applies settings defaults and does all type conversion.
311 318
312 319 We would move all settings parsing and preparation into this place, so that
313 320 we have only one place left which deals with this part. The remaining parts
314 321 of the application would start to rely fully on well prepared settings.
315 322
316 323 This piece would later be split up per topic to avoid a big fat monster
317 324 function.
318 325 """
319 326
320 327 # Pyramid's mako renderer has to search in the templates folder so that the
321 328 # old templates still work. Ported and new templates are expected to use
322 329 # real asset specifications for the includes.
323 330 mako_directories = settings.setdefault('mako.directories', [
324 331 # Base templates of the original Pylons application
325 332 'rhodecode:templates',
326 333 ])
327 334 log.debug(
328 335 "Using the following Mako template directories: %s",
329 336 mako_directories)
330 337
331 338 # Default includes, possible to change as a user
332 339 pyramid_includes = settings.setdefault('pyramid.includes', [
333 340 'rhodecode.lib.middleware.request_wrapper',
334 341 ])
335 342 log.debug(
336 343 "Using the following pyramid.includes: %s",
337 344 pyramid_includes)
338 345
339 346 # TODO: johbo: Re-think this, usually the call to config.include
340 347 # should allow to pass in a prefix.
341 348 settings.setdefault('rhodecode.api.url', '/_admin/api')
342 349
343 350 _bool_setting(settings, 'vcs.server.enable', 'true')
344 351 _bool_setting(settings, 'static_files', 'true')
345 352 _bool_setting(settings, 'is_test', 'false')
346 353
347 354 return settings
348 355
349 356
350 357 def _bool_setting(settings, name, default):
351 358 settings[name] = asbool(settings.get(name, default))
General Comments 0
You need to be logged in to leave comments. Login now