##// END OF EJS Templates
request-wrapper: ensure we wrap WHOLE request not just logic after http detection.
marcink -
r4157:3d2cea78 default
parent child Browse files
Show More
@@ -1,756 +1,754 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 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 24 import collections
25 25 import tempfile
26 26 import time
27 27
28 28 from paste.gzipper import make_gzip_middleware
29 29 import pyramid.events
30 30 from pyramid.wsgi import wsgiapp
31 31 from pyramid.authorization import ACLAuthorizationPolicy
32 32 from pyramid.config import Configurator
33 33 from pyramid.settings import asbool, aslist
34 34 from pyramid.httpexceptions import (
35 35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
36 36 from pyramid.renderers import render_to_response
37 37
38 38 from rhodecode.model import meta
39 39 from rhodecode.config import patches
40 40 from rhodecode.config import utils as config_utils
41 41 from rhodecode.config.environment import load_pyramid_environment
42 42
43 43 import rhodecode.events
44 44 from rhodecode.lib.middleware.vcs import VCSMiddleware
45 45 from rhodecode.lib.request import Request
46 46 from rhodecode.lib.vcs import VCSCommunicationError
47 47 from rhodecode.lib.exceptions import VCSServerUnavailable
48 48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
50 50 from rhodecode.lib.celerylib.loader import configure_celery
51 51 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
52 52 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
53 53 from rhodecode.lib.exc_tracking import store_exception
54 54 from rhodecode.subscribers import (
55 55 scan_repositories_if_enabled, write_js_routes_if_enabled,
56 56 write_metadata_if_needed, inject_app_settings)
57 57
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 62 def is_http_error(response):
63 63 # error which should have traceback
64 64 return response.status_code > 499
65 65
66 66
67 67 def should_load_all():
68 68 """
69 69 Returns if all application components should be loaded. In some cases it's
70 70 desired to skip apps loading for faster shell script execution
71 71 """
72 72 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
73 73 if ssh_cmd:
74 74 return False
75 75
76 76 return True
77 77
78 78
79 79 def make_pyramid_app(global_config, **settings):
80 80 """
81 81 Constructs the WSGI application based on Pyramid.
82 82
83 83 Specials:
84 84
85 85 * The application can also be integrated like a plugin via the call to
86 86 `includeme`. This is accompanied with the other utility functions which
87 87 are called. Changing this should be done with great care to not break
88 88 cases when these fragments are assembled from another place.
89 89
90 90 """
91 91
92 92 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
93 93 # will be replaced by the value of the environment variable "NAME" in this case.
94 94 start_time = time.time()
95 95
96 96 debug = asbool(global_config.get('debug'))
97 97 if debug:
98 98 enable_debug()
99 99
100 100 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
101 101
102 102 global_config = _substitute_values(global_config, environ)
103 103 settings = _substitute_values(settings, environ)
104 104
105 105 sanitize_settings_and_apply_defaults(global_config, settings)
106 106
107 107 config = Configurator(settings=settings)
108 108
109 109 # Apply compatibility patches
110 110 patches.inspect_getargspec()
111 111
112 112 load_pyramid_environment(global_config, settings)
113 113
114 114 # Static file view comes first
115 115 includeme_first(config)
116 116
117 117 includeme(config)
118 118
119 119 pyramid_app = config.make_wsgi_app()
120 120 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
121 121 pyramid_app.config = config
122 122
123 123 config.configure_celery(global_config['__file__'])
124 124 # creating the app uses a connection - return it after we are done
125 125 meta.Session.remove()
126 126 total_time = time.time() - start_time
127 127 log.info('Pyramid app `%s` created and configured in %.2fs',
128 128 pyramid_app.func_name, total_time)
129 129
130 130 return pyramid_app
131 131
132 132
133 133 def not_found_view(request):
134 134 """
135 135 This creates the view which should be registered as not-found-view to
136 136 pyramid.
137 137 """
138 138
139 139 if not getattr(request, 'vcs_call', None):
140 140 # handle like regular case with our error_handler
141 141 return error_handler(HTTPNotFound(), request)
142 142
143 143 # handle not found view as a vcs call
144 144 settings = request.registry.settings
145 145 ae_client = getattr(request, 'ae_client', None)
146 146 vcs_app = VCSMiddleware(
147 147 HTTPNotFound(), request.registry, settings,
148 148 appenlight_client=ae_client)
149 149
150 150 return wsgiapp(vcs_app)(None, request)
151 151
152 152
153 153 def error_handler(exception, request):
154 154 import rhodecode
155 155 from rhodecode.lib import helpers
156 156
157 157 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
158 158
159 159 base_response = HTTPInternalServerError()
160 160 # prefer original exception for the response since it may have headers set
161 161 if isinstance(exception, HTTPException):
162 162 base_response = exception
163 163 elif isinstance(exception, VCSCommunicationError):
164 164 base_response = VCSServerUnavailable()
165 165
166 166 if is_http_error(base_response):
167 167 log.exception(
168 168 'error occurred handling this request for path: %s', request.path)
169 169
170 170 error_explanation = base_response.explanation or str(base_response)
171 171 if base_response.status_code == 404:
172 172 error_explanation += " Optionally you don't have permission to access this page."
173 173 c = AttributeDict()
174 174 c.error_message = base_response.status
175 175 c.error_explanation = error_explanation
176 176 c.visual = AttributeDict()
177 177
178 178 c.visual.rhodecode_support_url = (
179 179 request.registry.settings.get('rhodecode_support_url') or
180 180 request.route_url('rhodecode_support')
181 181 )
182 182 c.redirect_time = 0
183 183 c.rhodecode_name = rhodecode_title
184 184 if not c.rhodecode_name:
185 185 c.rhodecode_name = 'Rhodecode'
186 186
187 187 c.causes = []
188 188 if is_http_error(base_response):
189 189 c.causes.append('Server is overloaded.')
190 190 c.causes.append('Server database connection is lost.')
191 191 c.causes.append('Server expected unhandled error.')
192 192
193 193 if hasattr(base_response, 'causes'):
194 194 c.causes = base_response.causes
195 195
196 196 c.messages = helpers.flash.pop_messages(request=request)
197 197
198 198 exc_info = sys.exc_info()
199 199 c.exception_id = id(exc_info)
200 200 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
201 201 or base_response.status_code > 499
202 202 c.exception_id_url = request.route_url(
203 203 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
204 204
205 205 if c.show_exception_id:
206 206 store_exception(c.exception_id, exc_info)
207 207
208 208 response = render_to_response(
209 209 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
210 210 response=base_response)
211 211
212 212 return response
213 213
214 214
215 215 def includeme_first(config):
216 216 # redirect automatic browser favicon.ico requests to correct place
217 217 def favicon_redirect(context, request):
218 218 return HTTPFound(
219 219 request.static_path('rhodecode:public/images/favicon.ico'))
220 220
221 221 config.add_view(favicon_redirect, route_name='favicon')
222 222 config.add_route('favicon', '/favicon.ico')
223 223
224 224 def robots_redirect(context, request):
225 225 return HTTPFound(
226 226 request.static_path('rhodecode:public/robots.txt'))
227 227
228 228 config.add_view(robots_redirect, route_name='robots')
229 229 config.add_route('robots', '/robots.txt')
230 230
231 231 config.add_static_view(
232 232 '_static/deform', 'deform:static')
233 233 config.add_static_view(
234 234 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
235 235
236 236
237 237 def includeme(config):
238 238 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
239 239 settings = config.registry.settings
240 240 config.set_request_factory(Request)
241 241
242 242 # plugin information
243 243 config.registry.rhodecode_plugins = collections.OrderedDict()
244 244
245 245 config.add_directive(
246 246 'register_rhodecode_plugin', register_rhodecode_plugin)
247 247
248 248 config.add_directive('configure_celery', configure_celery)
249 249
250 250 if asbool(settings.get('appenlight', 'false')):
251 251 config.include('appenlight_client.ext.pyramid_tween')
252 252
253 253 load_all = should_load_all()
254 254
255 255 # Includes which are required. The application would fail without them.
256 256 config.include('pyramid_mako')
257 257 config.include('rhodecode.lib.rc_beaker')
258 258 config.include('rhodecode.lib.rc_cache')
259 259
260 260 config.include('rhodecode.apps._base.navigation')
261 261 config.include('rhodecode.apps._base.subscribers')
262 262 config.include('rhodecode.tweens')
263 263 config.include('rhodecode.authentication')
264 264
265 265 if load_all:
266 266 config.include('rhodecode.integrations')
267 267
268 268 if load_all:
269 269 # load CE authentication plugins
270 270 config.include('rhodecode.authentication.plugins.auth_crowd')
271 271 config.include('rhodecode.authentication.plugins.auth_headers')
272 272 config.include('rhodecode.authentication.plugins.auth_jasig_cas')
273 273 config.include('rhodecode.authentication.plugins.auth_ldap')
274 274 config.include('rhodecode.authentication.plugins.auth_pam')
275 275 config.include('rhodecode.authentication.plugins.auth_rhodecode')
276 276 config.include('rhodecode.authentication.plugins.auth_token')
277 277
278 278 # Auto discover authentication plugins and include their configuration.
279 279 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
280 280 from rhodecode.authentication import discover_legacy_plugins
281 281 discover_legacy_plugins(config)
282 282
283 283 # apps
284 284 if load_all:
285 285 config.include('rhodecode.apps._base')
286 286 config.include('rhodecode.apps.hovercards')
287 287 config.include('rhodecode.apps.ops')
288 288 config.include('rhodecode.apps.admin')
289 289 config.include('rhodecode.apps.channelstream')
290 290 config.include('rhodecode.apps.file_store')
291 291 config.include('rhodecode.apps.login')
292 292 config.include('rhodecode.apps.home')
293 293 config.include('rhodecode.apps.journal')
294 294 config.include('rhodecode.apps.repository')
295 295 config.include('rhodecode.apps.repo_group')
296 296 config.include('rhodecode.apps.user_group')
297 297 config.include('rhodecode.apps.search')
298 298 config.include('rhodecode.apps.user_profile')
299 299 config.include('rhodecode.apps.user_group_profile')
300 300 config.include('rhodecode.apps.my_account')
301 301 config.include('rhodecode.apps.svn_support')
302 302 config.include('rhodecode.apps.ssh_support')
303 303 config.include('rhodecode.apps.gist')
304 304 config.include('rhodecode.apps.debug_style')
305 305 config.include('rhodecode.api')
306 306
307 307 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
308 308 config.add_translation_dirs('rhodecode:i18n/')
309 309 settings['default_locale_name'] = settings.get('lang', 'en')
310 310
311 311 # Add subscribers.
312 312 if load_all:
313 313 config.add_subscriber(inject_app_settings,
314 314 pyramid.events.ApplicationCreated)
315 315 config.add_subscriber(scan_repositories_if_enabled,
316 316 pyramid.events.ApplicationCreated)
317 317 config.add_subscriber(write_metadata_if_needed,
318 318 pyramid.events.ApplicationCreated)
319 319 config.add_subscriber(write_js_routes_if_enabled,
320 320 pyramid.events.ApplicationCreated)
321 321
322 322 # request custom methods
323 323 config.add_request_method(
324 324 'rhodecode.lib.partial_renderer.get_partial_renderer',
325 325 'get_partial_renderer')
326 326
327 327 config.add_request_method(
328 328 'rhodecode.lib.request_counter.get_request_counter',
329 329 'request_count')
330 330
331 331 # Set the authorization policy.
332 332 authz_policy = ACLAuthorizationPolicy()
333 333 config.set_authorization_policy(authz_policy)
334 334
335 335 # Set the default renderer for HTML templates to mako.
336 336 config.add_mako_renderer('.html')
337 337
338 338 config.add_renderer(
339 339 name='json_ext',
340 340 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
341 341
342 342 # include RhodeCode plugins
343 343 includes = aslist(settings.get('rhodecode.includes', []))
344 344 for inc in includes:
345 345 config.include(inc)
346 346
347 347 # custom not found view, if our pyramid app doesn't know how to handle
348 348 # the request pass it to potential VCS handling ap
349 349 config.add_notfound_view(not_found_view)
350 350 if not settings.get('debugtoolbar.enabled', False):
351 351 # disabled debugtoolbar handle all exceptions via the error_handlers
352 352 config.add_view(error_handler, context=Exception)
353 353
354 354 # all errors including 403/404/50X
355 355 config.add_view(error_handler, context=HTTPError)
356 356
357 357
358 358 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
359 359 """
360 360 Apply outer WSGI middlewares around the application.
361 361 """
362 362 registry = config.registry
363 363 settings = registry.settings
364 364
365 365 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
366 366 pyramid_app = HttpsFixup(pyramid_app, settings)
367 367
368 368 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
369 369 pyramid_app, settings)
370 370 registry.ae_client = _ae_client
371 371
372 372 if settings['gzip_responses']:
373 373 pyramid_app = make_gzip_middleware(
374 374 pyramid_app, settings, compress_level=1)
375 375
376 376 # this should be the outer most middleware in the wsgi stack since
377 377 # middleware like Routes make database calls
378 378 def pyramid_app_with_cleanup(environ, start_response):
379 379 try:
380 380 return pyramid_app(environ, start_response)
381 381 finally:
382 382 # Dispose current database session and rollback uncommitted
383 383 # transactions.
384 384 meta.Session.remove()
385 385
386 386 # In a single threaded mode server, on non sqlite db we should have
387 387 # '0 Current Checked out connections' at the end of a request,
388 388 # if not, then something, somewhere is leaving a connection open
389 389 pool = meta.Base.metadata.bind.engine.pool
390 390 log.debug('sa pool status: %s', pool.status())
391 391 log.debug('Request processing finalized')
392 392
393 393 return pyramid_app_with_cleanup
394 394
395 395
396 396 def sanitize_settings_and_apply_defaults(global_config, settings):
397 397 """
398 398 Applies settings defaults and does all type conversion.
399 399
400 400 We would move all settings parsing and preparation into this place, so that
401 401 we have only one place left which deals with this part. The remaining parts
402 402 of the application would start to rely fully on well prepared settings.
403 403
404 404 This piece would later be split up per topic to avoid a big fat monster
405 405 function.
406 406 """
407 407
408 408 settings.setdefault('rhodecode.edition', 'Community Edition')
409 409
410 410 if 'mako.default_filters' not in settings:
411 411 # set custom default filters if we don't have it defined
412 412 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
413 413 settings['mako.default_filters'] = 'h_filter'
414 414
415 415 if 'mako.directories' not in settings:
416 416 mako_directories = settings.setdefault('mako.directories', [
417 417 # Base templates of the original application
418 418 'rhodecode:templates',
419 419 ])
420 420 log.debug(
421 421 "Using the following Mako template directories: %s",
422 422 mako_directories)
423 423
424 424 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
425 425 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
426 426 raw_url = settings['beaker.session.url']
427 427 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
428 428 settings['beaker.session.url'] = 'redis://' + raw_url
429 429
430 430 # Default includes, possible to change as a user
431 pyramid_includes = settings.setdefault('pyramid.includes', [
432 'rhodecode.lib.middleware.request_wrapper',
433 ])
431 pyramid_includes = settings.setdefault('pyramid.includes', [])
434 432 log.debug(
435 433 "Using the following pyramid.includes: %s",
436 434 pyramid_includes)
437 435
438 436 # TODO: johbo: Re-think this, usually the call to config.include
439 437 # should allow to pass in a prefix.
440 438 settings.setdefault('rhodecode.api.url', '/_admin/api')
441 439 settings.setdefault('__file__', global_config.get('__file__'))
442 440
443 441 # Sanitize generic settings.
444 442 _list_setting(settings, 'default_encoding', 'UTF-8')
445 443 _bool_setting(settings, 'is_test', 'false')
446 444 _bool_setting(settings, 'gzip_responses', 'false')
447 445
448 446 # Call split out functions that sanitize settings for each topic.
449 447 _sanitize_appenlight_settings(settings)
450 448 _sanitize_vcs_settings(settings)
451 449 _sanitize_cache_settings(settings)
452 450
453 451 # configure instance id
454 452 config_utils.set_instance_id(settings)
455 453
456 454 return settings
457 455
458 456
459 457 def enable_debug():
460 458 """
461 459 Helper to enable debug on running instance
462 460 :return:
463 461 """
464 462 import tempfile
465 463 import textwrap
466 464 import logging.config
467 465
468 466 ini_template = textwrap.dedent("""
469 467 #####################################
470 468 ### DEBUG LOGGING CONFIGURATION ####
471 469 #####################################
472 470 [loggers]
473 471 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
474 472
475 473 [handlers]
476 474 keys = console, console_sql
477 475
478 476 [formatters]
479 477 keys = generic, color_formatter, color_formatter_sql
480 478
481 479 #############
482 480 ## LOGGERS ##
483 481 #############
484 482 [logger_root]
485 483 level = NOTSET
486 484 handlers = console
487 485
488 486 [logger_sqlalchemy]
489 487 level = INFO
490 488 handlers = console_sql
491 489 qualname = sqlalchemy.engine
492 490 propagate = 0
493 491
494 492 [logger_beaker]
495 493 level = DEBUG
496 494 handlers =
497 495 qualname = beaker.container
498 496 propagate = 1
499 497
500 498 [logger_rhodecode]
501 499 level = DEBUG
502 500 handlers =
503 501 qualname = rhodecode
504 502 propagate = 1
505 503
506 504 [logger_ssh_wrapper]
507 505 level = DEBUG
508 506 handlers =
509 507 qualname = ssh_wrapper
510 508 propagate = 1
511 509
512 510 [logger_celery]
513 511 level = DEBUG
514 512 handlers =
515 513 qualname = celery
516 514
517 515
518 516 ##############
519 517 ## HANDLERS ##
520 518 ##############
521 519
522 520 [handler_console]
523 521 class = StreamHandler
524 522 args = (sys.stderr, )
525 523 level = DEBUG
526 524 formatter = color_formatter
527 525
528 526 [handler_console_sql]
529 527 # "level = DEBUG" logs SQL queries and results.
530 528 # "level = INFO" logs SQL queries.
531 529 # "level = WARN" logs neither. (Recommended for production systems.)
532 530 class = StreamHandler
533 531 args = (sys.stderr, )
534 532 level = WARN
535 533 formatter = color_formatter_sql
536 534
537 535 ################
538 536 ## FORMATTERS ##
539 537 ################
540 538
541 539 [formatter_generic]
542 540 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
543 541 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
544 542 datefmt = %Y-%m-%d %H:%M:%S
545 543
546 544 [formatter_color_formatter]
547 545 class = rhodecode.lib.logging_formatter.ColorRequestTrackingFormatter
548 546 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
549 547 datefmt = %Y-%m-%d %H:%M:%S
550 548
551 549 [formatter_color_formatter_sql]
552 550 class = rhodecode.lib.logging_formatter.ColorFormatterSql
553 551 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
554 552 datefmt = %Y-%m-%d %H:%M:%S
555 553 """)
556 554
557 555 with tempfile.NamedTemporaryFile(prefix='rc_debug_logging_', suffix='.ini',
558 556 delete=False) as f:
559 557 log.info('Saved Temporary DEBUG config at %s', f.name)
560 558 f.write(ini_template)
561 559
562 560 logging.config.fileConfig(f.name)
563 561 log.debug('DEBUG MODE ON')
564 562 os.remove(f.name)
565 563
566 564
567 565 def _sanitize_appenlight_settings(settings):
568 566 _bool_setting(settings, 'appenlight', 'false')
569 567
570 568
571 569 def _sanitize_vcs_settings(settings):
572 570 """
573 571 Applies settings defaults and does type conversion for all VCS related
574 572 settings.
575 573 """
576 574 _string_setting(settings, 'vcs.svn.compatible_version', '')
577 575 _string_setting(settings, 'vcs.hooks.protocol', 'http')
578 576 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
579 577 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
580 578 _string_setting(settings, 'vcs.server', '')
581 579 _string_setting(settings, 'vcs.server.log_level', 'debug')
582 580 _string_setting(settings, 'vcs.server.protocol', 'http')
583 581 _bool_setting(settings, 'startup.import_repos', 'false')
584 582 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
585 583 _bool_setting(settings, 'vcs.server.enable', 'true')
586 584 _bool_setting(settings, 'vcs.start_server', 'false')
587 585 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
588 586 _int_setting(settings, 'vcs.connection_timeout', 3600)
589 587
590 588 # Support legacy values of vcs.scm_app_implementation. Legacy
591 589 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
592 590 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
593 591 scm_app_impl = settings['vcs.scm_app_implementation']
594 592 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
595 593 settings['vcs.scm_app_implementation'] = 'http'
596 594
597 595
598 596 def _sanitize_cache_settings(settings):
599 597 temp_store = tempfile.gettempdir()
600 598 default_cache_dir = os.path.join(temp_store, 'rc_cache')
601 599
602 600 # save default, cache dir, and use it for all backends later.
603 601 default_cache_dir = _string_setting(
604 602 settings,
605 603 'cache_dir',
606 604 default_cache_dir, lower=False, default_when_empty=True)
607 605
608 606 # ensure we have our dir created
609 607 if not os.path.isdir(default_cache_dir):
610 608 os.makedirs(default_cache_dir, mode=0o755)
611 609
612 610 # exception store cache
613 611 _string_setting(
614 612 settings,
615 613 'exception_tracker.store_path',
616 614 temp_store, lower=False, default_when_empty=True)
617 615
618 616 # cache_perms
619 617 _string_setting(
620 618 settings,
621 619 'rc_cache.cache_perms.backend',
622 620 'dogpile.cache.rc.file_namespace', lower=False)
623 621 _int_setting(
624 622 settings,
625 623 'rc_cache.cache_perms.expiration_time',
626 624 60)
627 625 _string_setting(
628 626 settings,
629 627 'rc_cache.cache_perms.arguments.filename',
630 628 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
631 629
632 630 # cache_repo
633 631 _string_setting(
634 632 settings,
635 633 'rc_cache.cache_repo.backend',
636 634 'dogpile.cache.rc.file_namespace', lower=False)
637 635 _int_setting(
638 636 settings,
639 637 'rc_cache.cache_repo.expiration_time',
640 638 60)
641 639 _string_setting(
642 640 settings,
643 641 'rc_cache.cache_repo.arguments.filename',
644 642 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
645 643
646 644 # cache_license
647 645 _string_setting(
648 646 settings,
649 647 'rc_cache.cache_license.backend',
650 648 'dogpile.cache.rc.file_namespace', lower=False)
651 649 _int_setting(
652 650 settings,
653 651 'rc_cache.cache_license.expiration_time',
654 652 5*60)
655 653 _string_setting(
656 654 settings,
657 655 'rc_cache.cache_license.arguments.filename',
658 656 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
659 657
660 658 # cache_repo_longterm memory, 96H
661 659 _string_setting(
662 660 settings,
663 661 'rc_cache.cache_repo_longterm.backend',
664 662 'dogpile.cache.rc.memory_lru', lower=False)
665 663 _int_setting(
666 664 settings,
667 665 'rc_cache.cache_repo_longterm.expiration_time',
668 666 345600)
669 667 _int_setting(
670 668 settings,
671 669 'rc_cache.cache_repo_longterm.max_size',
672 670 10000)
673 671
674 672 # sql_cache_short
675 673 _string_setting(
676 674 settings,
677 675 'rc_cache.sql_cache_short.backend',
678 676 'dogpile.cache.rc.memory_lru', lower=False)
679 677 _int_setting(
680 678 settings,
681 679 'rc_cache.sql_cache_short.expiration_time',
682 680 30)
683 681 _int_setting(
684 682 settings,
685 683 'rc_cache.sql_cache_short.max_size',
686 684 10000)
687 685
688 686
689 687 def _int_setting(settings, name, default):
690 688 settings[name] = int(settings.get(name, default))
691 689 return settings[name]
692 690
693 691
694 692 def _bool_setting(settings, name, default):
695 693 input_val = settings.get(name, default)
696 694 if isinstance(input_val, unicode):
697 695 input_val = input_val.encode('utf8')
698 696 settings[name] = asbool(input_val)
699 697 return settings[name]
700 698
701 699
702 700 def _list_setting(settings, name, default):
703 701 raw_value = settings.get(name, default)
704 702
705 703 old_separator = ','
706 704 if old_separator in raw_value:
707 705 # If we get a comma separated list, pass it to our own function.
708 706 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
709 707 else:
710 708 # Otherwise we assume it uses pyramids space/newline separation.
711 709 settings[name] = aslist(raw_value)
712 710 return settings[name]
713 711
714 712
715 713 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
716 714 value = settings.get(name, default)
717 715
718 716 if default_when_empty and not value:
719 717 # use default value when value is empty
720 718 value = default
721 719
722 720 if lower:
723 721 value = value.lower()
724 722 settings[name] = value
725 723 return settings[name]
726 724
727 725
728 726 def _substitute_values(mapping, substitutions):
729 727 result = {}
730 728
731 729 try:
732 730 for key, value in mapping.items():
733 731 # initialize without substitution first
734 732 result[key] = value
735 733
736 734 # Note: Cannot use regular replacements, since they would clash
737 735 # with the implementation of ConfigParser. Using "format" instead.
738 736 try:
739 737 result[key] = value.format(**substitutions)
740 738 except KeyError as e:
741 739 env_var = '{}'.format(e.args[0])
742 740
743 741 msg = 'Failed to substitute: `{key}={{{var}}}` with environment entry. ' \
744 742 'Make sure your environment has {var} set, or remove this ' \
745 743 'variable from config file'.format(key=key, var=env_var)
746 744
747 745 if env_var.startswith('ENV_'):
748 746 raise ValueError(msg)
749 747 else:
750 748 log.warning(msg)
751 749
752 750 except ValueError as e:
753 751 log.warning('Failed to substitute ENV variable: %s', e)
754 752 result = mapping
755 753
756 754 return result
@@ -1,61 +1,62 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 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 time
22 22 import logging
23 23
24 24 import rhodecode
25 25 from rhodecode.lib.base import get_ip_addr, get_access_path, get_user_agent
26 26 from rhodecode.lib.utils2 import safe_str
27 27
28 28
29 29 log = logging.getLogger(__name__)
30 30
31 31
32 32 class RequestWrapperTween(object):
33 33 def __init__(self, handler, registry):
34 34 self.handler = handler
35 35 self.registry = registry
36 36
37 37 # one-time configuration code goes here
38 38
39 39 def __call__(self, request):
40 40 start = time.time()
41 log.debug('Starting request time measurement')
41 42 try:
42 43 response = self.handler(request)
43 44 finally:
44 45 end = time.time()
45 46 total = end - start
46 47 count = request.request_count()
47 48 _ver_ = rhodecode.__version__
48 49 log.info(
49 50 'Req[%4s] IP: %s %s Request to %s time: %.4fs [%s], RhodeCode %s',
50 51 count, get_ip_addr(request.environ), request.environ.get('REQUEST_METHOD'),
51 52 safe_str(get_access_path(request.environ)), total,
52 53 get_user_agent(request. environ), _ver_
53 54 )
54 55
55 56 return response
56 57
57 58
58 59 def includeme(config):
59 60 config.add_tween(
60 61 'rhodecode.lib.middleware.request_wrapper.RequestWrapperTween',
61 62 )
@@ -1,118 +1,121 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 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 import logging
23 23 from pyramid.httpexceptions import HTTPException, HTTPBadRequest
24 24
25 25 from rhodecode.lib.middleware.vcs import (
26 26 detect_vcs_request, VCS_TYPE_KEY, VCS_TYPE_SKIP)
27 27
28 28
29 29 log = logging.getLogger(__name__)
30 30
31 31
32 32 def vcs_detection_tween_factory(handler, registry):
33 33
34 34 def vcs_detection_tween(request):
35 35 """
36 36 Do detection of vcs type, and save results for other layers to re-use
37 37 this information
38 38 """
39 39 vcs_server_enabled = request.registry.settings.get('vcs.server.enable')
40 40 vcs_handler = vcs_server_enabled and detect_vcs_request(
41 41 request.environ, request.registry.settings.get('vcs.backends'))
42 42
43 43 if vcs_handler:
44 44 # save detected VCS type for later re-use
45 45 request.environ[VCS_TYPE_KEY] = vcs_handler.SCM
46 46 request.vcs_call = vcs_handler.SCM
47 47
48 48 log.debug('Processing request with `%s` handler', handler)
49 49 return handler(request)
50 50
51 51 # mark that we didn't detect an VCS, and we can skip detection later on
52 52 request.environ[VCS_TYPE_KEY] = VCS_TYPE_SKIP
53 53
54 54 log.debug('Processing request with `%s` handler', handler)
55 55 return handler(request)
56 56
57 57 return vcs_detection_tween
58 58
59 59
60 60 def junk_encoding_detector(request):
61 61 """
62 62 Detect bad encoded GET params, and fail immediately with BadRequest
63 63 """
64 64
65 65 try:
66 66 request.GET.get("", None)
67 67 except UnicodeDecodeError:
68 68 raise HTTPBadRequest("Invalid bytes in query string.")
69 69
70 70
71 71 def bad_url_data_detector(request):
72 72 """
73 73 Detect invalid bytes in a path.
74 74 """
75 75 try:
76 76 request.path_info
77 77 except UnicodeDecodeError:
78 78 raise HTTPBadRequest("Invalid bytes in URL.")
79 79
80 80
81 81 def junk_form_data_detector(request):
82 82 """
83 83 Detect bad encoded POST params, and fail immediately with BadRequest
84 84 """
85 85
86 86 if request.method == "POST":
87 87 try:
88 88 request.POST.get("", None)
89 89 except ValueError:
90 90 raise HTTPBadRequest("Invalid bytes in form data.")
91 91
92 92
93 93 def sanity_check_factory(handler, registry):
94 94 def sanity_check(request):
95 95 log.debug('Checking current URL sanity for bad data')
96 96 try:
97 97 junk_encoding_detector(request)
98 98 bad_url_data_detector(request)
99 99 junk_form_data_detector(request)
100 100 except HTTPException as exc:
101 101 return exc
102 102
103 103 return handler(request)
104 104
105 105 return sanity_check
106 106
107 107
108 108 def includeme(config):
109 109 config.add_subscriber('rhodecode.subscribers.add_renderer_globals',
110 110 'pyramid.events.BeforeRender')
111 111 config.add_subscriber('rhodecode.subscribers.set_user_lang',
112 112 'pyramid.events.NewRequest')
113 113 config.add_subscriber('rhodecode.subscribers.add_localizer',
114 114 'pyramid.events.NewRequest')
115 115 config.add_subscriber('rhodecode.subscribers.add_request_user_context',
116 116 'pyramid.events.ContextFound')
117 117 config.add_tween('rhodecode.tweens.vcs_detection_tween_factory')
118 118 config.add_tween('rhodecode.tweens.sanity_check_factory')
119
120 # This needs to be the LAST item
121 config.add_tween('rhodecode.lib.middleware.request_wrapper.RequestWrapperTween')
General Comments 0
You need to be logged in to leave comments. Login now