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