##// END OF EJS Templates
middleware: use finalizing log to inidicate we finished ALL operations.
marcink -
r2922:40395251 default
parent child Browse files
Show More
@@ -1,518 +1,518 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 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 import os
22 22 import sys
23 23 import logging
24 import traceback
25 24 import collections
26 25 import tempfile
27 26
28 27 from paste.gzipper import make_gzip_middleware
29 28 from pyramid.wsgi import wsgiapp
30 29 from pyramid.authorization import ACLAuthorizationPolicy
31 30 from pyramid.config import Configurator
32 31 from pyramid.settings import asbool, aslist
33 32 from pyramid.httpexceptions import (
34 33 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
35 34 from pyramid.events import ApplicationCreated
36 35 from pyramid.renderers import render_to_response
37 36
38 37 from rhodecode.model import meta
39 38 from rhodecode.config import patches
40 39 from rhodecode.config import utils as config_utils
41 40 from rhodecode.config.environment import load_pyramid_environment
42 41
43 42 from rhodecode.lib.middleware.vcs import VCSMiddleware
44 43 from rhodecode.lib.request import Request
45 44 from rhodecode.lib.vcs import VCSCommunicationError
46 45 from rhodecode.lib.exceptions import VCSServerUnavailable
47 46 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
48 47 from rhodecode.lib.middleware.https_fixup import HttpsFixup
49 48 from rhodecode.lib.celerylib.loader import configure_celery
50 49 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
51 50 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
52 51 from rhodecode.lib.exc_tracking import store_exception
53 52 from rhodecode.subscribers import (
54 53 scan_repositories_if_enabled, write_js_routes_if_enabled,
55 54 write_metadata_if_needed, inject_app_settings)
56 55
57 56
58 57 log = logging.getLogger(__name__)
59 58
60 59
61 60 def is_http_error(response):
62 61 # error which should have traceback
63 62 return response.status_code > 499
64 63
65 64
66 65 def make_pyramid_app(global_config, **settings):
67 66 """
68 67 Constructs the WSGI application based on Pyramid.
69 68
70 69 Specials:
71 70
72 71 * The application can also be integrated like a plugin via the call to
73 72 `includeme`. This is accompanied with the other utility functions which
74 73 are called. Changing this should be done with great care to not break
75 74 cases when these fragments are assembled from another place.
76 75
77 76 """
78 77
79 78 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
80 79 # will be replaced by the value of the environment variable "NAME" in this case.
81 80 environ = {
82 81 'ENV_{}'.format(key): value for key, value in os.environ.items()}
83 82
84 83 global_config = _substitute_values(global_config, environ)
85 84 settings = _substitute_values(settings, environ)
86 85
87 86 sanitize_settings_and_apply_defaults(settings)
88 87
89 88 config = Configurator(settings=settings)
90 89
91 90 # Apply compatibility patches
92 91 patches.inspect_getargspec()
93 92
94 93 load_pyramid_environment(global_config, settings)
95 94
96 95 # Static file view comes first
97 96 includeme_first(config)
98 97
99 98 includeme(config)
100 99
101 100 pyramid_app = config.make_wsgi_app()
102 101 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
103 102 pyramid_app.config = config
104 103
105 104 config.configure_celery(global_config['__file__'])
106 105 # creating the app uses a connection - return it after we are done
107 106 meta.Session.remove()
108 107
109 108 log.info('Pyramid app %s created and configured.', pyramid_app)
110 109 return pyramid_app
111 110
112 111
113 112 def not_found_view(request):
114 113 """
115 114 This creates the view which should be registered as not-found-view to
116 115 pyramid.
117 116 """
118 117
119 118 if not getattr(request, 'vcs_call', None):
120 119 # handle like regular case with our error_handler
121 120 return error_handler(HTTPNotFound(), request)
122 121
123 122 # handle not found view as a vcs call
124 123 settings = request.registry.settings
125 124 ae_client = getattr(request, 'ae_client', None)
126 125 vcs_app = VCSMiddleware(
127 126 HTTPNotFound(), request.registry, settings,
128 127 appenlight_client=ae_client)
129 128
130 129 return wsgiapp(vcs_app)(None, request)
131 130
132 131
133 132 def error_handler(exception, request):
134 133 import rhodecode
135 134 from rhodecode.lib import helpers
136 135
137 136 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
138 137
139 138 base_response = HTTPInternalServerError()
140 139 # prefer original exception for the response since it may have headers set
141 140 if isinstance(exception, HTTPException):
142 141 base_response = exception
143 142 elif isinstance(exception, VCSCommunicationError):
144 143 base_response = VCSServerUnavailable()
145 144
146 145 if is_http_error(base_response):
147 146 log.exception(
148 147 'error occurred handling this request for path: %s', request.path)
149 148
150 149 error_explanation = base_response.explanation or str(base_response)
151 150 if base_response.status_code == 404:
152 151 error_explanation += " Or you don't have permission to access it."
153 152 c = AttributeDict()
154 153 c.error_message = base_response.status
155 154 c.error_explanation = error_explanation
156 155 c.visual = AttributeDict()
157 156
158 157 c.visual.rhodecode_support_url = (
159 158 request.registry.settings.get('rhodecode_support_url') or
160 159 request.route_url('rhodecode_support')
161 160 )
162 161 c.redirect_time = 0
163 162 c.rhodecode_name = rhodecode_title
164 163 if not c.rhodecode_name:
165 164 c.rhodecode_name = 'Rhodecode'
166 165
167 166 c.causes = []
168 167 if is_http_error(base_response):
169 168 c.causes.append('Server is overloaded.')
170 169 c.causes.append('Server database connection is lost.')
171 170 c.causes.append('Server expected unhandled error.')
172 171
173 172 if hasattr(base_response, 'causes'):
174 173 c.causes = base_response.causes
175 174
176 175 c.messages = helpers.flash.pop_messages(request=request)
177 176
178 177 exc_info = sys.exc_info()
179 178 c.exception_id = id(exc_info)
180 179 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
181 180 or base_response.status_code > 499
182 181 c.exception_id_url = request.route_url(
183 182 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
184 183
185 184 if c.show_exception_id:
186 185 store_exception(c.exception_id, exc_info)
187 186
188 187 response = render_to_response(
189 188 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
190 189 response=base_response)
191 190
192 191 return response
193 192
194 193
195 194 def includeme_first(config):
196 195 # redirect automatic browser favicon.ico requests to correct place
197 196 def favicon_redirect(context, request):
198 197 return HTTPFound(
199 198 request.static_path('rhodecode:public/images/favicon.ico'))
200 199
201 200 config.add_view(favicon_redirect, route_name='favicon')
202 201 config.add_route('favicon', '/favicon.ico')
203 202
204 203 def robots_redirect(context, request):
205 204 return HTTPFound(
206 205 request.static_path('rhodecode:public/robots.txt'))
207 206
208 207 config.add_view(robots_redirect, route_name='robots')
209 208 config.add_route('robots', '/robots.txt')
210 209
211 210 config.add_static_view(
212 211 '_static/deform', 'deform:static')
213 212 config.add_static_view(
214 213 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
215 214
216 215
217 216 def includeme(config):
218 217 settings = config.registry.settings
219 218 config.set_request_factory(Request)
220 219
221 220 # plugin information
222 221 config.registry.rhodecode_plugins = collections.OrderedDict()
223 222
224 223 config.add_directive(
225 224 'register_rhodecode_plugin', register_rhodecode_plugin)
226 225
227 226 config.add_directive('configure_celery', configure_celery)
228 227
229 228 if asbool(settings.get('appenlight', 'false')):
230 229 config.include('appenlight_client.ext.pyramid_tween')
231 230
232 231 # Includes which are required. The application would fail without them.
233 232 config.include('pyramid_mako')
234 233 config.include('pyramid_beaker')
235 234 config.include('rhodecode.lib.caches')
236 235 config.include('rhodecode.lib.rc_cache')
237 236
238 237 config.include('rhodecode.authentication')
239 238 config.include('rhodecode.integrations')
240 239
241 240 # apps
242 241 config.include('rhodecode.apps._base')
243 242 config.include('rhodecode.apps.ops')
244 243
245 244 config.include('rhodecode.apps.admin')
246 245 config.include('rhodecode.apps.channelstream')
247 246 config.include('rhodecode.apps.login')
248 247 config.include('rhodecode.apps.home')
249 248 config.include('rhodecode.apps.journal')
250 249 config.include('rhodecode.apps.repository')
251 250 config.include('rhodecode.apps.repo_group')
252 251 config.include('rhodecode.apps.user_group')
253 252 config.include('rhodecode.apps.search')
254 253 config.include('rhodecode.apps.user_profile')
255 254 config.include('rhodecode.apps.user_group_profile')
256 255 config.include('rhodecode.apps.my_account')
257 256 config.include('rhodecode.apps.svn_support')
258 257 config.include('rhodecode.apps.ssh_support')
259 258 config.include('rhodecode.apps.gist')
260 259
261 260 config.include('rhodecode.apps.debug_style')
262 261 config.include('rhodecode.tweens')
263 262 config.include('rhodecode.api')
264 263
265 264 config.add_route(
266 265 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
267 266
268 267 config.add_translation_dirs('rhodecode:i18n/')
269 268 settings['default_locale_name'] = settings.get('lang', 'en')
270 269
271 270 # Add subscribers.
272 271 config.add_subscriber(inject_app_settings, ApplicationCreated)
273 272 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
274 273 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
275 274 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
276 275
277 276
278 277 # request custom methods
279 278 config.add_request_method(
280 279 'rhodecode.lib.partial_renderer.get_partial_renderer',
281 280 'get_partial_renderer')
282 281
283 282 # Set the authorization policy.
284 283 authz_policy = ACLAuthorizationPolicy()
285 284 config.set_authorization_policy(authz_policy)
286 285
287 286 # Set the default renderer for HTML templates to mako.
288 287 config.add_mako_renderer('.html')
289 288
290 289 config.add_renderer(
291 290 name='json_ext',
292 291 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
293 292
294 293 # include RhodeCode plugins
295 294 includes = aslist(settings.get('rhodecode.includes', []))
296 295 for inc in includes:
297 296 config.include(inc)
298 297
299 298 # custom not found view, if our pyramid app doesn't know how to handle
300 299 # the request pass it to potential VCS handling ap
301 300 config.add_notfound_view(not_found_view)
302 301 if not settings.get('debugtoolbar.enabled', False):
303 302 # disabled debugtoolbar handle all exceptions via the error_handlers
304 303 config.add_view(error_handler, context=Exception)
305 304
306 305 # all errors including 403/404/50X
307 306 config.add_view(error_handler, context=HTTPError)
308 307
309 308
310 309 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
311 310 """
312 311 Apply outer WSGI middlewares around the application.
313 312 """
314 313 registry = config.registry
315 314 settings = registry.settings
316 315
317 316 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
318 317 pyramid_app = HttpsFixup(pyramid_app, settings)
319 318
320 319 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
321 320 pyramid_app, settings)
322 321 registry.ae_client = _ae_client
323 322
324 323 if settings['gzip_responses']:
325 324 pyramid_app = make_gzip_middleware(
326 325 pyramid_app, settings, compress_level=1)
327 326
328 327 # this should be the outer most middleware in the wsgi stack since
329 328 # middleware like Routes make database calls
330 329 def pyramid_app_with_cleanup(environ, start_response):
331 330 try:
332 331 return pyramid_app(environ, start_response)
333 332 finally:
334 333 # Dispose current database session and rollback uncommitted
335 334 # transactions.
336 335 meta.Session.remove()
337 336
338 337 # In a single threaded mode server, on non sqlite db we should have
339 338 # '0 Current Checked out connections' at the end of a request,
340 339 # if not, then something, somewhere is leaving a connection open
341 340 pool = meta.Base.metadata.bind.engine.pool
342 341 log.debug('sa pool status: %s', pool.status())
342 log.debug('Request processing finalized')
343 343
344 344 return pyramid_app_with_cleanup
345 345
346 346
347 347 def sanitize_settings_and_apply_defaults(settings):
348 348 """
349 349 Applies settings defaults and does all type conversion.
350 350
351 351 We would move all settings parsing and preparation into this place, so that
352 352 we have only one place left which deals with this part. The remaining parts
353 353 of the application would start to rely fully on well prepared settings.
354 354
355 355 This piece would later be split up per topic to avoid a big fat monster
356 356 function.
357 357 """
358 358
359 359 settings.setdefault('rhodecode.edition', 'Community Edition')
360 360
361 361 if 'mako.default_filters' not in settings:
362 362 # set custom default filters if we don't have it defined
363 363 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
364 364 settings['mako.default_filters'] = 'h_filter'
365 365
366 366 if 'mako.directories' not in settings:
367 367 mako_directories = settings.setdefault('mako.directories', [
368 368 # Base templates of the original application
369 369 'rhodecode:templates',
370 370 ])
371 371 log.debug(
372 372 "Using the following Mako template directories: %s",
373 373 mako_directories)
374 374
375 375 # Default includes, possible to change as a user
376 376 pyramid_includes = settings.setdefault('pyramid.includes', [
377 377 'rhodecode.lib.middleware.request_wrapper',
378 378 ])
379 379 log.debug(
380 380 "Using the following pyramid.includes: %s",
381 381 pyramid_includes)
382 382
383 383 # TODO: johbo: Re-think this, usually the call to config.include
384 384 # should allow to pass in a prefix.
385 385 settings.setdefault('rhodecode.api.url', '/_admin/api')
386 386
387 387 # Sanitize generic settings.
388 388 _list_setting(settings, 'default_encoding', 'UTF-8')
389 389 _bool_setting(settings, 'is_test', 'false')
390 390 _bool_setting(settings, 'gzip_responses', 'false')
391 391
392 392 # Call split out functions that sanitize settings for each topic.
393 393 _sanitize_appenlight_settings(settings)
394 394 _sanitize_vcs_settings(settings)
395 395 _sanitize_cache_settings(settings)
396 396
397 397 # configure instance id
398 398 config_utils.set_instance_id(settings)
399 399
400 400 return settings
401 401
402 402
403 403 def _sanitize_appenlight_settings(settings):
404 404 _bool_setting(settings, 'appenlight', 'false')
405 405
406 406
407 407 def _sanitize_vcs_settings(settings):
408 408 """
409 409 Applies settings defaults and does type conversion for all VCS related
410 410 settings.
411 411 """
412 412 _string_setting(settings, 'vcs.svn.compatible_version', '')
413 413 _string_setting(settings, 'git_rev_filter', '--all')
414 414 _string_setting(settings, 'vcs.hooks.protocol', 'http')
415 415 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
416 416 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
417 417 _string_setting(settings, 'vcs.server', '')
418 418 _string_setting(settings, 'vcs.server.log_level', 'debug')
419 419 _string_setting(settings, 'vcs.server.protocol', 'http')
420 420 _bool_setting(settings, 'startup.import_repos', 'false')
421 421 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
422 422 _bool_setting(settings, 'vcs.server.enable', 'true')
423 423 _bool_setting(settings, 'vcs.start_server', 'false')
424 424 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
425 425 _int_setting(settings, 'vcs.connection_timeout', 3600)
426 426
427 427 # Support legacy values of vcs.scm_app_implementation. Legacy
428 428 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
429 429 # which is now mapped to 'http'.
430 430 scm_app_impl = settings['vcs.scm_app_implementation']
431 431 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
432 432 settings['vcs.scm_app_implementation'] = 'http'
433 433
434 434
435 435 def _sanitize_cache_settings(settings):
436 436 _string_setting(settings, 'cache_dir',
437 437 os.path.join(tempfile.gettempdir(), 'rc_cache'))
438 438 # cache_perms
439 439 _string_setting(
440 440 settings,
441 441 'rc_cache.cache_perms.backend',
442 442 'dogpile.cache.rc.file_namespace')
443 443 _int_setting(
444 444 settings,
445 445 'rc_cache.cache_perms.expiration_time',
446 446 60)
447 447 _string_setting(
448 448 settings,
449 449 'rc_cache.cache_perms.arguments.filename',
450 450 os.path.join(tempfile.gettempdir(), 'rc_cache_1'))
451 451
452 452 # cache_repo
453 453 _string_setting(
454 454 settings,
455 455 'rc_cache.cache_repo.backend',
456 456 'dogpile.cache.rc.file_namespace')
457 457 _int_setting(
458 458 settings,
459 459 'rc_cache.cache_repo.expiration_time',
460 460 60)
461 461 _string_setting(
462 462 settings,
463 463 'rc_cache.cache_repo.arguments.filename',
464 464 os.path.join(tempfile.gettempdir(), 'rc_cache_2'))
465 465
466 466 # sql_cache_short
467 467 _string_setting(
468 468 settings,
469 469 'rc_cache.sql_cache_short.backend',
470 470 'dogpile.cache.rc.memory_lru')
471 471 _int_setting(
472 472 settings,
473 473 'rc_cache.sql_cache_short.expiration_time',
474 474 30)
475 475 _int_setting(
476 476 settings,
477 477 'rc_cache.sql_cache_short.max_size',
478 478 10000)
479 479
480 480
481 481 def _int_setting(settings, name, default):
482 482 settings[name] = int(settings.get(name, default))
483 483
484 484
485 485 def _bool_setting(settings, name, default):
486 486 input_val = settings.get(name, default)
487 487 if isinstance(input_val, unicode):
488 488 input_val = input_val.encode('utf8')
489 489 settings[name] = asbool(input_val)
490 490
491 491
492 492 def _list_setting(settings, name, default):
493 493 raw_value = settings.get(name, default)
494 494
495 495 old_separator = ','
496 496 if old_separator in raw_value:
497 497 # If we get a comma separated list, pass it to our own function.
498 498 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
499 499 else:
500 500 # Otherwise we assume it uses pyramids space/newline separation.
501 501 settings[name] = aslist(raw_value)
502 502
503 503
504 504 def _string_setting(settings, name, default, lower=True):
505 505 value = settings.get(name, default)
506 506 if lower:
507 507 value = value.lower()
508 508 settings[name] = value
509 509
510 510
511 511 def _substitute_values(mapping, substitutions):
512 512 result = {
513 513 # Note: Cannot use regular replacements, since they would clash
514 514 # with the implementation of ConfigParser. Using "format" instead.
515 515 key: value.format(**substitutions)
516 516 for key, value in mapping.items()
517 517 }
518 518 return result
General Comments 0
You need to be logged in to leave comments. Login now