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