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