##// END OF EJS Templates
error-page: use custom causes for 500+ errors
marcink -
r2116:91c04731 default
parent child Browse files
Show More
@@ -1,545 +1,551 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Pylons middleware initialization
22 Pylons middleware initialization
23 """
23 """
24 import logging
24 import logging
25 import traceback
25 import traceback
26 from collections import OrderedDict
26 from collections import OrderedDict
27
27
28 from paste.registry import RegistryManager
28 from paste.registry import RegistryManager
29 from paste.gzipper import make_gzip_middleware
29 from paste.gzipper import make_gzip_middleware
30 from pylons.wsgiapp import PylonsApp
30 from pylons.wsgiapp import PylonsApp
31 from pyramid.authorization import ACLAuthorizationPolicy
31 from pyramid.authorization import ACLAuthorizationPolicy
32 from pyramid.config import Configurator
32 from pyramid.config import Configurator
33 from pyramid.settings import asbool, aslist
33 from pyramid.settings import asbool, aslist
34 from pyramid.wsgi import wsgiapp
34 from pyramid.wsgi import wsgiapp
35 from pyramid.httpexceptions import (
35 from pyramid.httpexceptions import (
36 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound)
36 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound)
37 from pyramid.events import ApplicationCreated
37 from pyramid.events import ApplicationCreated
38 from pyramid.renderers import render_to_response
38 from pyramid.renderers import render_to_response
39 from routes.middleware import RoutesMiddleware
39 from routes.middleware import RoutesMiddleware
40 import rhodecode
40 import rhodecode
41
41
42 from rhodecode.model import meta
42 from rhodecode.model import meta
43 from rhodecode.config import patches
43 from rhodecode.config import patches
44 from rhodecode.config import utils as config_utils
44 from rhodecode.config import utils as config_utils
45 from rhodecode.config.routing import STATIC_FILE_PREFIX
45 from rhodecode.config.routing import STATIC_FILE_PREFIX
46 from rhodecode.config.environment import (
46 from rhodecode.config.environment import (
47 load_environment, load_pyramid_environment)
47 load_environment, load_pyramid_environment)
48
48
49 from rhodecode.lib.vcs import VCSCommunicationError
49 from rhodecode.lib.vcs import VCSCommunicationError
50 from rhodecode.lib.exceptions import VCSServerUnavailable
50 from rhodecode.lib.exceptions import VCSServerUnavailable
51 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
51 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
52 from rhodecode.lib.middleware.error_handling import (
52 from rhodecode.lib.middleware.error_handling import (
53 PylonsErrorHandlingMiddleware)
53 PylonsErrorHandlingMiddleware)
54 from rhodecode.lib.middleware.https_fixup import HttpsFixup
54 from rhodecode.lib.middleware.https_fixup import HttpsFixup
55 from rhodecode.lib.middleware.vcs import VCSMiddleware
55 from rhodecode.lib.middleware.vcs import VCSMiddleware
56 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
56 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
57 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
57 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
58 from rhodecode.subscribers import (
58 from rhodecode.subscribers import (
59 scan_repositories_if_enabled, write_js_routes_if_enabled,
59 scan_repositories_if_enabled, write_js_routes_if_enabled,
60 write_metadata_if_needed, inject_app_settings)
60 write_metadata_if_needed, inject_app_settings)
61
61
62
62
63 log = logging.getLogger(__name__)
63 log = logging.getLogger(__name__)
64
64
65
65
66 # this is used to avoid avoid the route lookup overhead in routesmiddleware
66 # this is used to avoid avoid the route lookup overhead in routesmiddleware
67 # for certain routes which won't go to pylons to - eg. static files, debugger
67 # for certain routes which won't go to pylons to - eg. static files, debugger
68 # it is only needed for the pylons migration and can be removed once complete
68 # it is only needed for the pylons migration and can be removed once complete
69 class SkippableRoutesMiddleware(RoutesMiddleware):
69 class SkippableRoutesMiddleware(RoutesMiddleware):
70 """ Routes middleware that allows you to skip prefixes """
70 """ Routes middleware that allows you to skip prefixes """
71
71
72 def __init__(self, *args, **kw):
72 def __init__(self, *args, **kw):
73 self.skip_prefixes = kw.pop('skip_prefixes', [])
73 self.skip_prefixes = kw.pop('skip_prefixes', [])
74 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
74 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
75
75
76 def __call__(self, environ, start_response):
76 def __call__(self, environ, start_response):
77 for prefix in self.skip_prefixes:
77 for prefix in self.skip_prefixes:
78 if environ['PATH_INFO'].startswith(prefix):
78 if environ['PATH_INFO'].startswith(prefix):
79 # added to avoid the case when a missing /_static route falls
79 # added to avoid the case when a missing /_static route falls
80 # through to pylons and causes an exception as pylons is
80 # through to pylons and causes an exception as pylons is
81 # expecting wsgiorg.routingargs to be set in the environ
81 # expecting wsgiorg.routingargs to be set in the environ
82 # by RoutesMiddleware.
82 # by RoutesMiddleware.
83 if 'wsgiorg.routing_args' not in environ:
83 if 'wsgiorg.routing_args' not in environ:
84 environ['wsgiorg.routing_args'] = (None, {})
84 environ['wsgiorg.routing_args'] = (None, {})
85 return self.app(environ, start_response)
85 return self.app(environ, start_response)
86
86
87 return super(SkippableRoutesMiddleware, self).__call__(
87 return super(SkippableRoutesMiddleware, self).__call__(
88 environ, start_response)
88 environ, start_response)
89
89
90
90
91 def make_app(global_conf, static_files=True, **app_conf):
91 def make_app(global_conf, static_files=True, **app_conf):
92 """Create a Pylons WSGI application and return it
92 """Create a Pylons WSGI application and return it
93
93
94 ``global_conf``
94 ``global_conf``
95 The inherited configuration for this application. Normally from
95 The inherited configuration for this application. Normally from
96 the [DEFAULT] section of the Paste ini file.
96 the [DEFAULT] section of the Paste ini file.
97
97
98 ``app_conf``
98 ``app_conf``
99 The application's local configuration. Normally specified in
99 The application's local configuration. Normally specified in
100 the [app:<name>] section of the Paste ini file (where <name>
100 the [app:<name>] section of the Paste ini file (where <name>
101 defaults to main).
101 defaults to main).
102
102
103 """
103 """
104 # Apply compatibility patches
104 # Apply compatibility patches
105 patches.kombu_1_5_1_python_2_7_11()
105 patches.kombu_1_5_1_python_2_7_11()
106 patches.inspect_getargspec()
106 patches.inspect_getargspec()
107
107
108 # Configure the Pylons environment
108 # Configure the Pylons environment
109 config = load_environment(global_conf, app_conf)
109 config = load_environment(global_conf, app_conf)
110
110
111 # The Pylons WSGI app
111 # The Pylons WSGI app
112 app = PylonsApp(config=config)
112 app = PylonsApp(config=config)
113
113
114 # Establish the Registry for this application
114 # Establish the Registry for this application
115 app = RegistryManager(app)
115 app = RegistryManager(app)
116
116
117 app.config = config
117 app.config = config
118
118
119 return app
119 return app
120
120
121
121
122 def make_pyramid_app(global_config, **settings):
122 def make_pyramid_app(global_config, **settings):
123 """
123 """
124 Constructs the WSGI application based on Pyramid and wraps the Pylons based
124 Constructs the WSGI application based on Pyramid and wraps the Pylons based
125 application.
125 application.
126
126
127 Specials:
127 Specials:
128
128
129 * We migrate from Pylons to Pyramid. While doing this, we keep both
129 * We migrate from Pylons to Pyramid. While doing this, we keep both
130 frameworks functional. This involves moving some WSGI middlewares around
130 frameworks functional. This involves moving some WSGI middlewares around
131 and providing access to some data internals, so that the old code is
131 and providing access to some data internals, so that the old code is
132 still functional.
132 still functional.
133
133
134 * The application can also be integrated like a plugin via the call to
134 * The application can also be integrated like a plugin via the call to
135 `includeme`. This is accompanied with the other utility functions which
135 `includeme`. This is accompanied with the other utility functions which
136 are called. Changing this should be done with great care to not break
136 are called. Changing this should be done with great care to not break
137 cases when these fragments are assembled from another place.
137 cases when these fragments are assembled from another place.
138
138
139 """
139 """
140 # The edition string should be available in pylons too, so we add it here
140 # The edition string should be available in pylons too, so we add it here
141 # before copying the settings.
141 # before copying the settings.
142 settings.setdefault('rhodecode.edition', 'Community Edition')
142 settings.setdefault('rhodecode.edition', 'Community Edition')
143
143
144 # As long as our Pylons application does expect "unprepared" settings, make
144 # As long as our Pylons application does expect "unprepared" settings, make
145 # sure that we keep an unmodified copy. This avoids unintentional change of
145 # sure that we keep an unmodified copy. This avoids unintentional change of
146 # behavior in the old application.
146 # behavior in the old application.
147 settings_pylons = settings.copy()
147 settings_pylons = settings.copy()
148
148
149 sanitize_settings_and_apply_defaults(settings)
149 sanitize_settings_and_apply_defaults(settings)
150
150
151 config = Configurator(settings=settings)
151 config = Configurator(settings=settings)
152 load_pyramid_environment(global_config, settings)
152 load_pyramid_environment(global_config, settings)
153
153
154 add_pylons_compat_data(config.registry, global_config, settings_pylons)
154 add_pylons_compat_data(config.registry, global_config, settings_pylons)
155
155
156 includeme_first(config)
156 includeme_first(config)
157 includeme(config)
157 includeme(config)
158
158
159 pyramid_app = config.make_wsgi_app()
159 pyramid_app = config.make_wsgi_app()
160 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
160 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
161 pyramid_app.config = config
161 pyramid_app.config = config
162
162
163 # creating the app uses a connection - return it after we are done
163 # creating the app uses a connection - return it after we are done
164 meta.Session.remove()
164 meta.Session.remove()
165
165
166 return pyramid_app
166 return pyramid_app
167
167
168
168
169 def make_not_found_view(config):
169 def make_not_found_view(config):
170 """
170 """
171 This creates the view which should be registered as not-found-view to
171 This creates the view which should be registered as not-found-view to
172 pyramid. Basically it contains of the old pylons app, converted to a view.
172 pyramid. Basically it contains of the old pylons app, converted to a view.
173 Additionally it is wrapped by some other middlewares.
173 Additionally it is wrapped by some other middlewares.
174 """
174 """
175 settings = config.registry.settings
175 settings = config.registry.settings
176 vcs_server_enabled = settings['vcs.server.enable']
176 vcs_server_enabled = settings['vcs.server.enable']
177
177
178 # Make pylons app from unprepared settings.
178 # Make pylons app from unprepared settings.
179 pylons_app = make_app(
179 pylons_app = make_app(
180 config.registry._pylons_compat_global_config,
180 config.registry._pylons_compat_global_config,
181 **config.registry._pylons_compat_settings)
181 **config.registry._pylons_compat_settings)
182 config.registry._pylons_compat_config = pylons_app.config
182 config.registry._pylons_compat_config = pylons_app.config
183
183
184 # Appenlight monitoring.
184 # Appenlight monitoring.
185 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
185 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
186 pylons_app, settings)
186 pylons_app, settings)
187
187
188 # The pylons app is executed inside of the pyramid 404 exception handler.
188 # The pylons app is executed inside of the pyramid 404 exception handler.
189 # Exceptions which are raised inside of it are not handled by pyramid
189 # Exceptions which are raised inside of it are not handled by pyramid
190 # again. Therefore we add a middleware that invokes the error handler in
190 # again. Therefore we add a middleware that invokes the error handler in
191 # case of an exception or error response. This way we return proper error
191 # case of an exception or error response. This way we return proper error
192 # HTML pages in case of an error.
192 # HTML pages in case of an error.
193 reraise = (settings.get('debugtoolbar.enabled', False) or
193 reraise = (settings.get('debugtoolbar.enabled', False) or
194 rhodecode.disable_error_handler)
194 rhodecode.disable_error_handler)
195 pylons_app = PylonsErrorHandlingMiddleware(
195 pylons_app = PylonsErrorHandlingMiddleware(
196 pylons_app, error_handler, reraise)
196 pylons_app, error_handler, reraise)
197
197
198 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
198 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
199 # view to handle the request. Therefore it is wrapped around the pylons
199 # view to handle the request. Therefore it is wrapped around the pylons
200 # app. It has to be outside of the error handling otherwise error responses
200 # app. It has to be outside of the error handling otherwise error responses
201 # from the vcsserver are converted to HTML error pages. This confuses the
201 # from the vcsserver are converted to HTML error pages. This confuses the
202 # command line tools and the user won't get a meaningful error message.
202 # command line tools and the user won't get a meaningful error message.
203 if vcs_server_enabled:
203 if vcs_server_enabled:
204 pylons_app = VCSMiddleware(
204 pylons_app = VCSMiddleware(
205 pylons_app, settings, appenlight_client, registry=config.registry)
205 pylons_app, settings, appenlight_client, registry=config.registry)
206
206
207 # Convert WSGI app to pyramid view and return it.
207 # Convert WSGI app to pyramid view and return it.
208 return wsgiapp(pylons_app)
208 return wsgiapp(pylons_app)
209
209
210
210
211 def add_pylons_compat_data(registry, global_config, settings):
211 def add_pylons_compat_data(registry, global_config, settings):
212 """
212 """
213 Attach data to the registry to support the Pylons integration.
213 Attach data to the registry to support the Pylons integration.
214 """
214 """
215 registry._pylons_compat_global_config = global_config
215 registry._pylons_compat_global_config = global_config
216 registry._pylons_compat_settings = settings
216 registry._pylons_compat_settings = settings
217
217
218
218
219 def error_handler(exception, request):
219 def error_handler(exception, request):
220 import rhodecode
220 import rhodecode
221 from rhodecode.lib import helpers
221 from rhodecode.lib import helpers
222
222
223 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
223 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
224
224
225 base_response = HTTPInternalServerError()
225 base_response = HTTPInternalServerError()
226 # prefer original exception for the response since it may have headers set
226 # prefer original exception for the response since it may have headers set
227 if isinstance(exception, HTTPException):
227 if isinstance(exception, HTTPException):
228 base_response = exception
228 base_response = exception
229 elif isinstance(exception, VCSCommunicationError):
229 elif isinstance(exception, VCSCommunicationError):
230 base_response = VCSServerUnavailable()
230 base_response = VCSServerUnavailable()
231
231
232 def is_http_error(response):
232 def is_http_error(response):
233 # error which should have traceback
233 # error which should have traceback
234 return response.status_code > 499
234 return response.status_code > 499
235
235
236 if is_http_error(base_response):
236 if is_http_error(base_response):
237 log.exception(
237 log.exception(
238 'error occurred handling this request for path: %s', request.path)
238 'error occurred handling this request for path: %s', request.path)
239
239
240 error_explanation = base_response.explanation or str(base_response)
240 error_explanation = base_response.explanation or str(base_response)
241 if base_response.status_code == 404:
241 if base_response.status_code == 404:
242 error_explanation += " Or you don't have permission to access it."
242 error_explanation += " Or you don't have permission to access it."
243 c = AttributeDict()
243 c = AttributeDict()
244 c.error_message = base_response.status
244 c.error_message = base_response.status
245 c.error_explanation = error_explanation
245 c.error_explanation = error_explanation
246 c.visual = AttributeDict()
246 c.visual = AttributeDict()
247
247
248 c.visual.rhodecode_support_url = (
248 c.visual.rhodecode_support_url = (
249 request.registry.settings.get('rhodecode_support_url') or
249 request.registry.settings.get('rhodecode_support_url') or
250 request.route_url('rhodecode_support')
250 request.route_url('rhodecode_support')
251 )
251 )
252 c.redirect_time = 0
252 c.redirect_time = 0
253 c.rhodecode_name = rhodecode_title
253 c.rhodecode_name = rhodecode_title
254 if not c.rhodecode_name:
254 if not c.rhodecode_name:
255 c.rhodecode_name = 'Rhodecode'
255 c.rhodecode_name = 'Rhodecode'
256
256
257 c.causes = []
257 c.causes = []
258 if is_http_error(base_response):
259 c.causes.append('Server is overloaded.')
260 c.causes.append('Server database connection is lost.')
261 c.causes.append('Server expected unhandled error.')
262
258 if hasattr(base_response, 'causes'):
263 if hasattr(base_response, 'causes'):
259 c.causes = base_response.causes
264 c.causes = base_response.causes
265
260 c.messages = helpers.flash.pop_messages(request=request)
266 c.messages = helpers.flash.pop_messages(request=request)
261 c.traceback = traceback.format_exc()
267 c.traceback = traceback.format_exc()
262 response = render_to_response(
268 response = render_to_response(
263 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
269 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
264 response=base_response)
270 response=base_response)
265
271
266 return response
272 return response
267
273
268
274
269 def includeme(config):
275 def includeme(config):
270 settings = config.registry.settings
276 settings = config.registry.settings
271
277
272 # plugin information
278 # plugin information
273 config.registry.rhodecode_plugins = OrderedDict()
279 config.registry.rhodecode_plugins = OrderedDict()
274
280
275 config.add_directive(
281 config.add_directive(
276 'register_rhodecode_plugin', register_rhodecode_plugin)
282 'register_rhodecode_plugin', register_rhodecode_plugin)
277
283
278 if asbool(settings.get('appenlight', 'false')):
284 if asbool(settings.get('appenlight', 'false')):
279 config.include('appenlight_client.ext.pyramid_tween')
285 config.include('appenlight_client.ext.pyramid_tween')
280
286
281 if 'mako.default_filters' not in settings:
287 if 'mako.default_filters' not in settings:
282 # set custom default filters if we don't have it defined
288 # set custom default filters if we don't have it defined
283 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
289 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
284 settings['mako.default_filters'] = 'h_filter'
290 settings['mako.default_filters'] = 'h_filter'
285
291
286 # Includes which are required. The application would fail without them.
292 # Includes which are required. The application would fail without them.
287 config.include('pyramid_mako')
293 config.include('pyramid_mako')
288 config.include('pyramid_beaker')
294 config.include('pyramid_beaker')
289
295
290 config.include('rhodecode.authentication')
296 config.include('rhodecode.authentication')
291 config.include('rhodecode.integrations')
297 config.include('rhodecode.integrations')
292
298
293 # apps
299 # apps
294 config.include('rhodecode.apps._base')
300 config.include('rhodecode.apps._base')
295 config.include('rhodecode.apps.ops')
301 config.include('rhodecode.apps.ops')
296
302
297 config.include('rhodecode.apps.admin')
303 config.include('rhodecode.apps.admin')
298 config.include('rhodecode.apps.channelstream')
304 config.include('rhodecode.apps.channelstream')
299 config.include('rhodecode.apps.login')
305 config.include('rhodecode.apps.login')
300 config.include('rhodecode.apps.home')
306 config.include('rhodecode.apps.home')
301 config.include('rhodecode.apps.journal')
307 config.include('rhodecode.apps.journal')
302 config.include('rhodecode.apps.repository')
308 config.include('rhodecode.apps.repository')
303 config.include('rhodecode.apps.repo_group')
309 config.include('rhodecode.apps.repo_group')
304 config.include('rhodecode.apps.user_group')
310 config.include('rhodecode.apps.user_group')
305 config.include('rhodecode.apps.search')
311 config.include('rhodecode.apps.search')
306 config.include('rhodecode.apps.user_profile')
312 config.include('rhodecode.apps.user_profile')
307 config.include('rhodecode.apps.my_account')
313 config.include('rhodecode.apps.my_account')
308 config.include('rhodecode.apps.svn_support')
314 config.include('rhodecode.apps.svn_support')
309 config.include('rhodecode.apps.ssh_support')
315 config.include('rhodecode.apps.ssh_support')
310 config.include('rhodecode.apps.gist')
316 config.include('rhodecode.apps.gist')
311
317
312 config.include('rhodecode.apps.debug_style')
318 config.include('rhodecode.apps.debug_style')
313 config.include('rhodecode.tweens')
319 config.include('rhodecode.tweens')
314 config.include('rhodecode.api')
320 config.include('rhodecode.api')
315
321
316 config.add_route(
322 config.add_route(
317 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
323 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
318
324
319 config.add_translation_dirs('rhodecode:i18n/')
325 config.add_translation_dirs('rhodecode:i18n/')
320 settings['default_locale_name'] = settings.get('lang', 'en')
326 settings['default_locale_name'] = settings.get('lang', 'en')
321
327
322 # Add subscribers.
328 # Add subscribers.
323 config.add_subscriber(inject_app_settings, ApplicationCreated)
329 config.add_subscriber(inject_app_settings, ApplicationCreated)
324 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
330 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
325 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
331 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
326 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
332 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
327
333
328 config.add_request_method(
334 config.add_request_method(
329 'rhodecode.lib.partial_renderer.get_partial_renderer',
335 'rhodecode.lib.partial_renderer.get_partial_renderer',
330 'get_partial_renderer')
336 'get_partial_renderer')
331
337
332 # events
338 # events
333 # TODO(marcink): this should be done when pyramid migration is finished
339 # TODO(marcink): this should be done when pyramid migration is finished
334 # config.add_subscriber(
340 # config.add_subscriber(
335 # 'rhodecode.integrations.integrations_event_handler',
341 # 'rhodecode.integrations.integrations_event_handler',
336 # 'rhodecode.events.RhodecodeEvent')
342 # 'rhodecode.events.RhodecodeEvent')
337
343
338 # Set the authorization policy.
344 # Set the authorization policy.
339 authz_policy = ACLAuthorizationPolicy()
345 authz_policy = ACLAuthorizationPolicy()
340 config.set_authorization_policy(authz_policy)
346 config.set_authorization_policy(authz_policy)
341
347
342 # Set the default renderer for HTML templates to mako.
348 # Set the default renderer for HTML templates to mako.
343 config.add_mako_renderer('.html')
349 config.add_mako_renderer('.html')
344
350
345 config.add_renderer(
351 config.add_renderer(
346 name='json_ext',
352 name='json_ext',
347 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
353 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
348
354
349 # include RhodeCode plugins
355 # include RhodeCode plugins
350 includes = aslist(settings.get('rhodecode.includes', []))
356 includes = aslist(settings.get('rhodecode.includes', []))
351 for inc in includes:
357 for inc in includes:
352 config.include(inc)
358 config.include(inc)
353
359
354 # This is the glue which allows us to migrate in chunks. By registering the
360 # This is the glue which allows us to migrate in chunks. By registering the
355 # pylons based application as the "Not Found" view in Pyramid, we will
361 # pylons based application as the "Not Found" view in Pyramid, we will
356 # fallback to the old application each time the new one does not yet know
362 # fallback to the old application each time the new one does not yet know
357 # how to handle a request.
363 # how to handle a request.
358 config.add_notfound_view(make_not_found_view(config))
364 config.add_notfound_view(make_not_found_view(config))
359
365
360 if not settings.get('debugtoolbar.enabled', False):
366 if not settings.get('debugtoolbar.enabled', False):
361 # disabled debugtoolbar handle all exceptions via the error_handlers
367 # disabled debugtoolbar handle all exceptions via the error_handlers
362 config.add_view(error_handler, context=Exception)
368 config.add_view(error_handler, context=Exception)
363
369
364 config.add_view(error_handler, context=HTTPError)
370 config.add_view(error_handler, context=HTTPError)
365
371
366
372
367 def includeme_first(config):
373 def includeme_first(config):
368 # redirect automatic browser favicon.ico requests to correct place
374 # redirect automatic browser favicon.ico requests to correct place
369 def favicon_redirect(context, request):
375 def favicon_redirect(context, request):
370 return HTTPFound(
376 return HTTPFound(
371 request.static_path('rhodecode:public/images/favicon.ico'))
377 request.static_path('rhodecode:public/images/favicon.ico'))
372
378
373 config.add_view(favicon_redirect, route_name='favicon')
379 config.add_view(favicon_redirect, route_name='favicon')
374 config.add_route('favicon', '/favicon.ico')
380 config.add_route('favicon', '/favicon.ico')
375
381
376 def robots_redirect(context, request):
382 def robots_redirect(context, request):
377 return HTTPFound(
383 return HTTPFound(
378 request.static_path('rhodecode:public/robots.txt'))
384 request.static_path('rhodecode:public/robots.txt'))
379
385
380 config.add_view(robots_redirect, route_name='robots')
386 config.add_view(robots_redirect, route_name='robots')
381 config.add_route('robots', '/robots.txt')
387 config.add_route('robots', '/robots.txt')
382
388
383 config.add_static_view(
389 config.add_static_view(
384 '_static/deform', 'deform:static')
390 '_static/deform', 'deform:static')
385 config.add_static_view(
391 config.add_static_view(
386 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
392 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
387
393
388
394
389 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
395 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
390 """
396 """
391 Apply outer WSGI middlewares around the application.
397 Apply outer WSGI middlewares around the application.
392
398
393 Part of this has been moved up from the Pylons layer, so that the
399 Part of this has been moved up from the Pylons layer, so that the
394 data is also available if old Pylons code is hit through an already ported
400 data is also available if old Pylons code is hit through an already ported
395 view.
401 view.
396 """
402 """
397 settings = config.registry.settings
403 settings = config.registry.settings
398
404
399 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
405 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
400 pyramid_app = HttpsFixup(pyramid_app, settings)
406 pyramid_app = HttpsFixup(pyramid_app, settings)
401
407
402 # Add RoutesMiddleware to support the pylons compatibility tween during
408 # Add RoutesMiddleware to support the pylons compatibility tween during
403 # migration to pyramid.
409 # migration to pyramid.
404
410
405 # TODO(marcink): remove after migration to pyramid
411 # TODO(marcink): remove after migration to pyramid
406 if hasattr(config.registry, '_pylons_compat_config'):
412 if hasattr(config.registry, '_pylons_compat_config'):
407 routes_map = config.registry._pylons_compat_config['routes.map']
413 routes_map = config.registry._pylons_compat_config['routes.map']
408 pyramid_app = SkippableRoutesMiddleware(
414 pyramid_app = SkippableRoutesMiddleware(
409 pyramid_app, routes_map,
415 pyramid_app, routes_map,
410 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
416 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
411
417
412 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
418 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
413
419
414 if settings['gzip_responses']:
420 if settings['gzip_responses']:
415 pyramid_app = make_gzip_middleware(
421 pyramid_app = make_gzip_middleware(
416 pyramid_app, settings, compress_level=1)
422 pyramid_app, settings, compress_level=1)
417
423
418 # this should be the outer most middleware in the wsgi stack since
424 # this should be the outer most middleware in the wsgi stack since
419 # middleware like Routes make database calls
425 # middleware like Routes make database calls
420 def pyramid_app_with_cleanup(environ, start_response):
426 def pyramid_app_with_cleanup(environ, start_response):
421 try:
427 try:
422 return pyramid_app(environ, start_response)
428 return pyramid_app(environ, start_response)
423 finally:
429 finally:
424 # Dispose current database session and rollback uncommitted
430 # Dispose current database session and rollback uncommitted
425 # transactions.
431 # transactions.
426 meta.Session.remove()
432 meta.Session.remove()
427
433
428 # In a single threaded mode server, on non sqlite db we should have
434 # In a single threaded mode server, on non sqlite db we should have
429 # '0 Current Checked out connections' at the end of a request,
435 # '0 Current Checked out connections' at the end of a request,
430 # if not, then something, somewhere is leaving a connection open
436 # if not, then something, somewhere is leaving a connection open
431 pool = meta.Base.metadata.bind.engine.pool
437 pool = meta.Base.metadata.bind.engine.pool
432 log.debug('sa pool status: %s', pool.status())
438 log.debug('sa pool status: %s', pool.status())
433
439
434 return pyramid_app_with_cleanup
440 return pyramid_app_with_cleanup
435
441
436
442
437 def sanitize_settings_and_apply_defaults(settings):
443 def sanitize_settings_and_apply_defaults(settings):
438 """
444 """
439 Applies settings defaults and does all type conversion.
445 Applies settings defaults and does all type conversion.
440
446
441 We would move all settings parsing and preparation into this place, so that
447 We would move all settings parsing and preparation into this place, so that
442 we have only one place left which deals with this part. The remaining parts
448 we have only one place left which deals with this part. The remaining parts
443 of the application would start to rely fully on well prepared settings.
449 of the application would start to rely fully on well prepared settings.
444
450
445 This piece would later be split up per topic to avoid a big fat monster
451 This piece would later be split up per topic to avoid a big fat monster
446 function.
452 function.
447 """
453 """
448
454
449 # Pyramid's mako renderer has to search in the templates folder so that the
455 # Pyramid's mako renderer has to search in the templates folder so that the
450 # old templates still work. Ported and new templates are expected to use
456 # old templates still work. Ported and new templates are expected to use
451 # real asset specifications for the includes.
457 # real asset specifications for the includes.
452 mako_directories = settings.setdefault('mako.directories', [
458 mako_directories = settings.setdefault('mako.directories', [
453 # Base templates of the original Pylons application
459 # Base templates of the original Pylons application
454 'rhodecode:templates',
460 'rhodecode:templates',
455 ])
461 ])
456 log.debug(
462 log.debug(
457 "Using the following Mako template directories: %s",
463 "Using the following Mako template directories: %s",
458 mako_directories)
464 mako_directories)
459
465
460 # Default includes, possible to change as a user
466 # Default includes, possible to change as a user
461 pyramid_includes = settings.setdefault('pyramid.includes', [
467 pyramid_includes = settings.setdefault('pyramid.includes', [
462 'rhodecode.lib.middleware.request_wrapper',
468 'rhodecode.lib.middleware.request_wrapper',
463 ])
469 ])
464 log.debug(
470 log.debug(
465 "Using the following pyramid.includes: %s",
471 "Using the following pyramid.includes: %s",
466 pyramid_includes)
472 pyramid_includes)
467
473
468 # TODO: johbo: Re-think this, usually the call to config.include
474 # TODO: johbo: Re-think this, usually the call to config.include
469 # should allow to pass in a prefix.
475 # should allow to pass in a prefix.
470 settings.setdefault('rhodecode.api.url', '/_admin/api')
476 settings.setdefault('rhodecode.api.url', '/_admin/api')
471
477
472 # Sanitize generic settings.
478 # Sanitize generic settings.
473 _list_setting(settings, 'default_encoding', 'UTF-8')
479 _list_setting(settings, 'default_encoding', 'UTF-8')
474 _bool_setting(settings, 'is_test', 'false')
480 _bool_setting(settings, 'is_test', 'false')
475 _bool_setting(settings, 'gzip_responses', 'false')
481 _bool_setting(settings, 'gzip_responses', 'false')
476
482
477 # Call split out functions that sanitize settings for each topic.
483 # Call split out functions that sanitize settings for each topic.
478 _sanitize_appenlight_settings(settings)
484 _sanitize_appenlight_settings(settings)
479 _sanitize_vcs_settings(settings)
485 _sanitize_vcs_settings(settings)
480
486
481 # configure instance id
487 # configure instance id
482 config_utils.set_instance_id(settings)
488 config_utils.set_instance_id(settings)
483
489
484 return settings
490 return settings
485
491
486
492
487 def _sanitize_appenlight_settings(settings):
493 def _sanitize_appenlight_settings(settings):
488 _bool_setting(settings, 'appenlight', 'false')
494 _bool_setting(settings, 'appenlight', 'false')
489
495
490
496
491 def _sanitize_vcs_settings(settings):
497 def _sanitize_vcs_settings(settings):
492 """
498 """
493 Applies settings defaults and does type conversion for all VCS related
499 Applies settings defaults and does type conversion for all VCS related
494 settings.
500 settings.
495 """
501 """
496 _string_setting(settings, 'vcs.svn.compatible_version', '')
502 _string_setting(settings, 'vcs.svn.compatible_version', '')
497 _string_setting(settings, 'git_rev_filter', '--all')
503 _string_setting(settings, 'git_rev_filter', '--all')
498 _string_setting(settings, 'vcs.hooks.protocol', 'http')
504 _string_setting(settings, 'vcs.hooks.protocol', 'http')
499 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
505 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
500 _string_setting(settings, 'vcs.server', '')
506 _string_setting(settings, 'vcs.server', '')
501 _string_setting(settings, 'vcs.server.log_level', 'debug')
507 _string_setting(settings, 'vcs.server.log_level', 'debug')
502 _string_setting(settings, 'vcs.server.protocol', 'http')
508 _string_setting(settings, 'vcs.server.protocol', 'http')
503 _bool_setting(settings, 'startup.import_repos', 'false')
509 _bool_setting(settings, 'startup.import_repos', 'false')
504 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
510 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
505 _bool_setting(settings, 'vcs.server.enable', 'true')
511 _bool_setting(settings, 'vcs.server.enable', 'true')
506 _bool_setting(settings, 'vcs.start_server', 'false')
512 _bool_setting(settings, 'vcs.start_server', 'false')
507 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
513 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
508 _int_setting(settings, 'vcs.connection_timeout', 3600)
514 _int_setting(settings, 'vcs.connection_timeout', 3600)
509
515
510 # Support legacy values of vcs.scm_app_implementation. Legacy
516 # Support legacy values of vcs.scm_app_implementation. Legacy
511 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
517 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
512 # which is now mapped to 'http'.
518 # which is now mapped to 'http'.
513 scm_app_impl = settings['vcs.scm_app_implementation']
519 scm_app_impl = settings['vcs.scm_app_implementation']
514 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
520 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
515 settings['vcs.scm_app_implementation'] = 'http'
521 settings['vcs.scm_app_implementation'] = 'http'
516
522
517
523
518 def _int_setting(settings, name, default):
524 def _int_setting(settings, name, default):
519 settings[name] = int(settings.get(name, default))
525 settings[name] = int(settings.get(name, default))
520
526
521
527
522 def _bool_setting(settings, name, default):
528 def _bool_setting(settings, name, default):
523 input = settings.get(name, default)
529 input = settings.get(name, default)
524 if isinstance(input, unicode):
530 if isinstance(input, unicode):
525 input = input.encode('utf8')
531 input = input.encode('utf8')
526 settings[name] = asbool(input)
532 settings[name] = asbool(input)
527
533
528
534
529 def _list_setting(settings, name, default):
535 def _list_setting(settings, name, default):
530 raw_value = settings.get(name, default)
536 raw_value = settings.get(name, default)
531
537
532 old_separator = ','
538 old_separator = ','
533 if old_separator in raw_value:
539 if old_separator in raw_value:
534 # If we get a comma separated list, pass it to our own function.
540 # If we get a comma separated list, pass it to our own function.
535 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
541 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
536 else:
542 else:
537 # Otherwise we assume it uses pyramids space/newline separation.
543 # Otherwise we assume it uses pyramids space/newline separation.
538 settings[name] = aslist(raw_value)
544 settings[name] = aslist(raw_value)
539
545
540
546
541 def _string_setting(settings, name, default, lower=True):
547 def _string_setting(settings, name, default, lower=True):
542 value = settings.get(name, default)
548 value = settings.get(name, default)
543 if lower:
549 if lower:
544 value = value.lower()
550 value = value.lower()
545 settings[name] = value
551 settings[name] = value
General Comments 0
You need to be logged in to leave comments. Login now