##// END OF EJS Templates
wsgi-stack: Use the pylons error handling middleware.
Martin Bornhold -
r945:c9704d4f default
parent child Browse files
Show More
@@ -1,505 +1,463 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 HTTPError, HTTPInternalServerError, HTTPFound
34 from pyramid.httpexceptions import (
35 HTTPError, HTTPInternalServerError, HTTPFound)
35 from pyramid.events import ApplicationCreated
36 from pyramid.events import ApplicationCreated
36 import pyramid.httpexceptions as httpexceptions
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.exceptions import VCSServerUnavailable
48 from rhodecode.lib.vcs.exceptions import VCSCommunicationError
49 from rhodecode.lib.middleware import csrf
47 from rhodecode.lib.middleware import csrf
50 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 (
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 VCSMiddleware shall operate like a fallback if pyramid doesn't find
189 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find
190 # a view to handle the request. Therefore we wrap it around the pylons app.
190 # a view to handle the request. Therefore we wrap it around the pylons app.
191 if vcs_server_enabled:
191 if vcs_server_enabled:
192 pylons_app = VCSMiddleware(
192 pylons_app = VCSMiddleware(
193 pylons_app, settings, appenlight_client, registry=config.registry)
193 pylons_app, settings, appenlight_client, registry=config.registry)
194
194
195 pylons_app_as_view = wsgiapp(pylons_app)
195 # Add an error handling middleware to convert errors from the old pylons
196
196 # app into a proper error page response.
197 def pylons_app_with_error_handler(context, request):
197 reraise = (settings.get('debugtoolbar.enabled', False) or
198 """
198 rhodecode.disable_error_handler)
199 Handle exceptions from rc pylons app:
199 pylons_app = PylonsErrorHandlingMiddleware(
200
200 pylons_app, error_handler, reraise)
201 - old webob type exceptions get converted to pyramid exceptions
202 - pyramid exceptions are passed to the error handler view
203 """
204 def is_vcs_response(response):
205 return 'X-RhodeCode-Backend' in response.headers
206
207 def is_http_error(response):
208 # webob type error responses
209 return (400 <= response.status_int <= 599)
210
211 def is_error_handling_needed(response):
212 return is_http_error(response) and not is_vcs_response(response)
213
201
214 try:
202 # Convert WSGI app to pyramid view and return it.
215 response = pylons_app_as_view(context, request)
203 return wsgiapp(pylons_app)
216 if is_error_handling_needed(response):
217 response = webob_to_pyramid_http_response(response)
218 return error_handler(response, request)
219 except HTTPError as e: # pyramid type exceptions
220 return error_handler(e, request)
221 except Exception as e:
222 log.exception(e)
223
224 if (settings.get('debugtoolbar.enabled', False) or
225 rhodecode.disable_error_handler):
226 raise
227
228 if isinstance(e, VCSCommunicationError):
229 return error_handler(VCSServerUnavailable(), request)
230
231 return error_handler(HTTPInternalServerError(), request)
232
233 return response
234
235 return pylons_app_with_error_handler
236
204
237
205
238 def add_pylons_compat_data(registry, global_config, settings):
206 def add_pylons_compat_data(registry, global_config, settings):
239 """
207 """
240 Attach data to the registry to support the Pylons integration.
208 Attach data to the registry to support the Pylons integration.
241 """
209 """
242 registry._pylons_compat_global_config = global_config
210 registry._pylons_compat_global_config = global_config
243 registry._pylons_compat_settings = settings
211 registry._pylons_compat_settings = settings
244
212
245
213
246 def webob_to_pyramid_http_response(webob_response):
247 ResponseClass = httpexceptions.status_map[webob_response.status_int]
248 pyramid_response = ResponseClass(webob_response.status)
249 pyramid_response.status = webob_response.status
250 pyramid_response.headers.update(webob_response.headers)
251 if pyramid_response.headers['content-type'] == 'text/html':
252 pyramid_response.headers['content-type'] = 'text/html; charset=UTF-8'
253 return pyramid_response
254
255
256 def error_handler(exception, request):
214 def error_handler(exception, request):
257 from rhodecode.model.settings import SettingsModel
215 from rhodecode.model.settings import SettingsModel
258 from rhodecode.lib.utils2 import AttributeDict
216 from rhodecode.lib.utils2 import AttributeDict
259
217
260 try:
218 try:
261 rc_config = SettingsModel().get_all_settings()
219 rc_config = SettingsModel().get_all_settings()
262 except Exception:
220 except Exception:
263 log.exception('failed to fetch settings')
221 log.exception('failed to fetch settings')
264 rc_config = {}
222 rc_config = {}
265
223
266 base_response = HTTPInternalServerError()
224 base_response = HTTPInternalServerError()
267 # prefer original exception for the response since it may have headers set
225 # prefer original exception for the response since it may have headers set
268 if isinstance(exception, HTTPError):
226 if isinstance(exception, HTTPError):
269 base_response = exception
227 base_response = exception
270
228
271 c = AttributeDict()
229 c = AttributeDict()
272 c.error_message = base_response.status
230 c.error_message = base_response.status
273 c.error_explanation = base_response.explanation or str(base_response)
231 c.error_explanation = base_response.explanation or str(base_response)
274 c.visual = AttributeDict()
232 c.visual = AttributeDict()
275
233
276 c.visual.rhodecode_support_url = (
234 c.visual.rhodecode_support_url = (
277 request.registry.settings.get('rhodecode_support_url') or
235 request.registry.settings.get('rhodecode_support_url') or
278 request.route_url('rhodecode_support')
236 request.route_url('rhodecode_support')
279 )
237 )
280 c.redirect_time = 0
238 c.redirect_time = 0
281 c.rhodecode_name = rc_config.get('rhodecode_title', '')
239 c.rhodecode_name = rc_config.get('rhodecode_title', '')
282 if not c.rhodecode_name:
240 if not c.rhodecode_name:
283 c.rhodecode_name = 'Rhodecode'
241 c.rhodecode_name = 'Rhodecode'
284
242
285 c.causes = []
243 c.causes = []
286 if hasattr(base_response, 'causes'):
244 if hasattr(base_response, 'causes'):
287 c.causes = base_response.causes
245 c.causes = base_response.causes
288
246
289 response = render_to_response(
247 response = render_to_response(
290 '/errors/error_document.html', {'c': c}, request=request,
248 '/errors/error_document.html', {'c': c}, request=request,
291 response=base_response)
249 response=base_response)
292
250
293 return response
251 return response
294
252
295
253
296 def includeme(config):
254 def includeme(config):
297 settings = config.registry.settings
255 settings = config.registry.settings
298
256
299 # plugin information
257 # plugin information
300 config.registry.rhodecode_plugins = OrderedDict()
258 config.registry.rhodecode_plugins = OrderedDict()
301
259
302 config.add_directive(
260 config.add_directive(
303 'register_rhodecode_plugin', register_rhodecode_plugin)
261 'register_rhodecode_plugin', register_rhodecode_plugin)
304
262
305 if asbool(settings.get('appenlight', 'false')):
263 if asbool(settings.get('appenlight', 'false')):
306 config.include('appenlight_client.ext.pyramid_tween')
264 config.include('appenlight_client.ext.pyramid_tween')
307
265
308 # Includes which are required. The application would fail without them.
266 # Includes which are required. The application would fail without them.
309 config.include('pyramid_mako')
267 config.include('pyramid_mako')
310 config.include('pyramid_beaker')
268 config.include('pyramid_beaker')
311 config.include('rhodecode.channelstream')
269 config.include('rhodecode.channelstream')
312 config.include('rhodecode.admin')
270 config.include('rhodecode.admin')
313 config.include('rhodecode.authentication')
271 config.include('rhodecode.authentication')
314 config.include('rhodecode.integrations')
272 config.include('rhodecode.integrations')
315 config.include('rhodecode.login')
273 config.include('rhodecode.login')
316 config.include('rhodecode.tweens')
274 config.include('rhodecode.tweens')
317 config.include('rhodecode.api')
275 config.include('rhodecode.api')
318 config.include('rhodecode.svn_support')
276 config.include('rhodecode.svn_support')
319 config.add_route(
277 config.add_route(
320 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
278 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
321
279
322 # Add subscribers.
280 # Add subscribers.
323 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
281 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
324
282
325 # Set the authorization policy.
283 # Set the authorization policy.
326 authz_policy = ACLAuthorizationPolicy()
284 authz_policy = ACLAuthorizationPolicy()
327 config.set_authorization_policy(authz_policy)
285 config.set_authorization_policy(authz_policy)
328
286
329 # Set the default renderer for HTML templates to mako.
287 # Set the default renderer for HTML templates to mako.
330 config.add_mako_renderer('.html')
288 config.add_mako_renderer('.html')
331
289
332 # include RhodeCode plugins
290 # include RhodeCode plugins
333 includes = aslist(settings.get('rhodecode.includes', []))
291 includes = aslist(settings.get('rhodecode.includes', []))
334 for inc in includes:
292 for inc in includes:
335 config.include(inc)
293 config.include(inc)
336
294
337 # This is the glue which allows us to migrate in chunks. By registering the
295 # This is the glue which allows us to migrate in chunks. By registering the
338 # pylons based application as the "Not Found" view in Pyramid, we will
296 # pylons based application as the "Not Found" view in Pyramid, we will
339 # fallback to the old application each time the new one does not yet know
297 # fallback to the old application each time the new one does not yet know
340 # how to handle a request.
298 # how to handle a request.
341 config.add_notfound_view(make_not_found_view(config))
299 config.add_notfound_view(make_not_found_view(config))
342
300
343 if not settings.get('debugtoolbar.enabled', False):
301 if not settings.get('debugtoolbar.enabled', False):
344 # if no toolbar, then any exception gets caught and rendered
302 # if no toolbar, then any exception gets caught and rendered
345 config.add_view(error_handler, context=Exception)
303 config.add_view(error_handler, context=Exception)
346
304
347 config.add_view(error_handler, context=HTTPError)
305 config.add_view(error_handler, context=HTTPError)
348
306
349
307
350 def includeme_first(config):
308 def includeme_first(config):
351 # redirect automatic browser favicon.ico requests to correct place
309 # redirect automatic browser favicon.ico requests to correct place
352 def favicon_redirect(context, request):
310 def favicon_redirect(context, request):
353 return HTTPFound(
311 return HTTPFound(
354 request.static_path('rhodecode:public/images/favicon.ico'))
312 request.static_path('rhodecode:public/images/favicon.ico'))
355
313
356 config.add_view(favicon_redirect, route_name='favicon')
314 config.add_view(favicon_redirect, route_name='favicon')
357 config.add_route('favicon', '/favicon.ico')
315 config.add_route('favicon', '/favicon.ico')
358
316
359 config.add_static_view(
317 config.add_static_view(
360 '_static/deform', 'deform:static')
318 '_static/deform', 'deform:static')
361 config.add_static_view(
319 config.add_static_view(
362 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
320 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
363
321
364
322
365 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
323 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
366 """
324 """
367 Apply outer WSGI middlewares around the application.
325 Apply outer WSGI middlewares around the application.
368
326
369 Part of this has been moved up from the Pylons layer, so that the
327 Part of this has been moved up from the Pylons layer, so that the
370 data is also available if old Pylons code is hit through an already ported
328 data is also available if old Pylons code is hit through an already ported
371 view.
329 view.
372 """
330 """
373 settings = config.registry.settings
331 settings = config.registry.settings
374
332
375 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
333 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
376 pyramid_app = HttpsFixup(pyramid_app, settings)
334 pyramid_app = HttpsFixup(pyramid_app, settings)
377
335
378 # Add RoutesMiddleware to support the pylons compatibility tween during
336 # Add RoutesMiddleware to support the pylons compatibility tween during
379 # migration to pyramid.
337 # migration to pyramid.
380 pyramid_app = SkippableRoutesMiddleware(
338 pyramid_app = SkippableRoutesMiddleware(
381 pyramid_app, config.registry._pylons_compat_config['routes.map'],
339 pyramid_app, config.registry._pylons_compat_config['routes.map'],
382 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
340 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
383
341
384 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
342 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
385
343
386 if settings['gzip_responses']:
344 if settings['gzip_responses']:
387 pyramid_app = make_gzip_middleware(
345 pyramid_app = make_gzip_middleware(
388 pyramid_app, settings, compress_level=1)
346 pyramid_app, settings, compress_level=1)
389
347
390
348
391 # this should be the outer most middleware in the wsgi stack since
349 # this should be the outer most middleware in the wsgi stack since
392 # middleware like Routes make database calls
350 # middleware like Routes make database calls
393 def pyramid_app_with_cleanup(environ, start_response):
351 def pyramid_app_with_cleanup(environ, start_response):
394 try:
352 try:
395 return pyramid_app(environ, start_response)
353 return pyramid_app(environ, start_response)
396 finally:
354 finally:
397 # Dispose current database session and rollback uncommitted
355 # Dispose current database session and rollback uncommitted
398 # transactions.
356 # transactions.
399 meta.Session.remove()
357 meta.Session.remove()
400
358
401 # In a single threaded mode server, on non sqlite db we should have
359 # In a single threaded mode server, on non sqlite db we should have
402 # '0 Current Checked out connections' at the end of a request,
360 # '0 Current Checked out connections' at the end of a request,
403 # if not, then something, somewhere is leaving a connection open
361 # if not, then something, somewhere is leaving a connection open
404 pool = meta.Base.metadata.bind.engine.pool
362 pool = meta.Base.metadata.bind.engine.pool
405 log.debug('sa pool status: %s', pool.status())
363 log.debug('sa pool status: %s', pool.status())
406
364
407
365
408 return pyramid_app_with_cleanup
366 return pyramid_app_with_cleanup
409
367
410
368
411 def sanitize_settings_and_apply_defaults(settings):
369 def sanitize_settings_and_apply_defaults(settings):
412 """
370 """
413 Applies settings defaults and does all type conversion.
371 Applies settings defaults and does all type conversion.
414
372
415 We would move all settings parsing and preparation into this place, so that
373 We would move all settings parsing and preparation into this place, so that
416 we have only one place left which deals with this part. The remaining parts
374 we have only one place left which deals with this part. The remaining parts
417 of the application would start to rely fully on well prepared settings.
375 of the application would start to rely fully on well prepared settings.
418
376
419 This piece would later be split up per topic to avoid a big fat monster
377 This piece would later be split up per topic to avoid a big fat monster
420 function.
378 function.
421 """
379 """
422
380
423 # Pyramid's mako renderer has to search in the templates folder so that the
381 # Pyramid's mako renderer has to search in the templates folder so that the
424 # old templates still work. Ported and new templates are expected to use
382 # old templates still work. Ported and new templates are expected to use
425 # real asset specifications for the includes.
383 # real asset specifications for the includes.
426 mako_directories = settings.setdefault('mako.directories', [
384 mako_directories = settings.setdefault('mako.directories', [
427 # Base templates of the original Pylons application
385 # Base templates of the original Pylons application
428 'rhodecode:templates',
386 'rhodecode:templates',
429 ])
387 ])
430 log.debug(
388 log.debug(
431 "Using the following Mako template directories: %s",
389 "Using the following Mako template directories: %s",
432 mako_directories)
390 mako_directories)
433
391
434 # Default includes, possible to change as a user
392 # Default includes, possible to change as a user
435 pyramid_includes = settings.setdefault('pyramid.includes', [
393 pyramid_includes = settings.setdefault('pyramid.includes', [
436 'rhodecode.lib.middleware.request_wrapper',
394 'rhodecode.lib.middleware.request_wrapper',
437 ])
395 ])
438 log.debug(
396 log.debug(
439 "Using the following pyramid.includes: %s",
397 "Using the following pyramid.includes: %s",
440 pyramid_includes)
398 pyramid_includes)
441
399
442 # TODO: johbo: Re-think this, usually the call to config.include
400 # TODO: johbo: Re-think this, usually the call to config.include
443 # should allow to pass in a prefix.
401 # should allow to pass in a prefix.
444 settings.setdefault('rhodecode.api.url', '/_admin/api')
402 settings.setdefault('rhodecode.api.url', '/_admin/api')
445
403
446 # Sanitize generic settings.
404 # Sanitize generic settings.
447 _list_setting(settings, 'default_encoding', 'UTF-8')
405 _list_setting(settings, 'default_encoding', 'UTF-8')
448 _bool_setting(settings, 'is_test', 'false')
406 _bool_setting(settings, 'is_test', 'false')
449 _bool_setting(settings, 'gzip_responses', 'false')
407 _bool_setting(settings, 'gzip_responses', 'false')
450
408
451 # Call split out functions that sanitize settings for each topic.
409 # Call split out functions that sanitize settings for each topic.
452 _sanitize_appenlight_settings(settings)
410 _sanitize_appenlight_settings(settings)
453 _sanitize_vcs_settings(settings)
411 _sanitize_vcs_settings(settings)
454
412
455 return settings
413 return settings
456
414
457
415
458 def _sanitize_appenlight_settings(settings):
416 def _sanitize_appenlight_settings(settings):
459 _bool_setting(settings, 'appenlight', 'false')
417 _bool_setting(settings, 'appenlight', 'false')
460
418
461
419
462 def _sanitize_vcs_settings(settings):
420 def _sanitize_vcs_settings(settings):
463 """
421 """
464 Applies settings defaults and does type conversion for all VCS related
422 Applies settings defaults and does type conversion for all VCS related
465 settings.
423 settings.
466 """
424 """
467 _string_setting(settings, 'vcs.svn.compatible_version', '')
425 _string_setting(settings, 'vcs.svn.compatible_version', '')
468 _string_setting(settings, 'git_rev_filter', '--all')
426 _string_setting(settings, 'git_rev_filter', '--all')
469 _string_setting(settings, 'vcs.hooks.protocol', 'pyro4')
427 _string_setting(settings, 'vcs.hooks.protocol', 'pyro4')
470 _string_setting(settings, 'vcs.server', '')
428 _string_setting(settings, 'vcs.server', '')
471 _string_setting(settings, 'vcs.server.log_level', 'debug')
429 _string_setting(settings, 'vcs.server.log_level', 'debug')
472 _string_setting(settings, 'vcs.server.protocol', 'pyro4')
430 _string_setting(settings, 'vcs.server.protocol', 'pyro4')
473 _bool_setting(settings, 'startup.import_repos', 'false')
431 _bool_setting(settings, 'startup.import_repos', 'false')
474 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
432 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
475 _bool_setting(settings, 'vcs.server.enable', 'true')
433 _bool_setting(settings, 'vcs.server.enable', 'true')
476 _bool_setting(settings, 'vcs.start_server', 'false')
434 _bool_setting(settings, 'vcs.start_server', 'false')
477 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
435 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
478 _int_setting(settings, 'vcs.connection_timeout', 3600)
436 _int_setting(settings, 'vcs.connection_timeout', 3600)
479
437
480
438
481 def _int_setting(settings, name, default):
439 def _int_setting(settings, name, default):
482 settings[name] = int(settings.get(name, default))
440 settings[name] = int(settings.get(name, default))
483
441
484
442
485 def _bool_setting(settings, name, default):
443 def _bool_setting(settings, name, default):
486 input = settings.get(name, default)
444 input = settings.get(name, default)
487 if isinstance(input, unicode):
445 if isinstance(input, unicode):
488 input = input.encode('utf8')
446 input = input.encode('utf8')
489 settings[name] = asbool(input)
447 settings[name] = asbool(input)
490
448
491
449
492 def _list_setting(settings, name, default):
450 def _list_setting(settings, name, default):
493 raw_value = settings.get(name, default)
451 raw_value = settings.get(name, default)
494
452
495 old_separator = ','
453 old_separator = ','
496 if old_separator in raw_value:
454 if old_separator in raw_value:
497 # If we get a comma separated list, pass it to our own function.
455 # If we get a comma separated list, pass it to our own function.
498 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
456 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
499 else:
457 else:
500 # Otherwise we assume it uses pyramids space/newline separation.
458 # Otherwise we assume it uses pyramids space/newline separation.
501 settings[name] = aslist(raw_value)
459 settings[name] = aslist(raw_value)
502
460
503
461
504 def _string_setting(settings, name, default):
462 def _string_setting(settings, name, default):
505 settings[name] = settings.get(name, default).lower()
463 settings[name] = settings.get(name, default).lower()
General Comments 0
You need to be logged in to leave comments. Login now