##// END OF EJS Templates
statics: use cache_max_age with 24H to cache loaded static assets by browsers.
marcink -
r466:1daa1b79 default
parent child Browse files
Show More
@@ -1,392 +1,393 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
26 26 from paste.registry import RegistryManager
27 27 from paste.gzipper import make_gzip_middleware
28 28 from pylons.wsgiapp import PylonsApp
29 29 from pyramid.authorization import ACLAuthorizationPolicy
30 30 from pyramid.config import Configurator
31 31 from pyramid.static import static_view
32 32 from pyramid.settings import asbool, aslist
33 33 from pyramid.wsgi import wsgiapp
34 34 from pyramid.httpexceptions import HTTPError, HTTPInternalServerError
35 35 from pylons.controllers.util import abort, redirect
36 36 import pyramid.httpexceptions as httpexceptions
37 37 from pyramid.renderers import render_to_response, render
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
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 # this is used to avoid avoid the route lookup overhead in routesmiddleware
59 59 # for certain routes which won't go to pylons to - eg. static files, debugger
60 60 # it is only needed for the pylons migration and can be removed once complete
61 61 class SkippableRoutesMiddleware(RoutesMiddleware):
62 62 """ Routes middleware that allows you to skip prefixes """
63 63
64 64 def __init__(self, *args, **kw):
65 65 self.skip_prefixes = kw.pop('skip_prefixes', [])
66 66 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
67 67
68 68 def __call__(self, environ, start_response):
69 69 for prefix in self.skip_prefixes:
70 70 if environ['PATH_INFO'].startswith(prefix):
71 71 return self.app(environ, start_response)
72 72
73 73 return super(SkippableRoutesMiddleware, self).__call__(
74 74 environ, start_response)
75 75
76 76
77 77 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
78 78 """Create a Pylons WSGI application and return it
79 79
80 80 ``global_conf``
81 81 The inherited configuration for this application. Normally from
82 82 the [DEFAULT] section of the Paste ini file.
83 83
84 84 ``full_stack``
85 85 Whether or not this application provides a full WSGI stack (by
86 86 default, meaning it handles its own exceptions and errors).
87 87 Disable full_stack when this application is "managed" by
88 88 another WSGI middleware.
89 89
90 90 ``app_conf``
91 91 The application's local configuration. Normally specified in
92 92 the [app:<name>] section of the Paste ini file (where <name>
93 93 defaults to main).
94 94
95 95 """
96 96 # Apply compatibility patches
97 97 patches.kombu_1_5_1_python_2_7_11()
98 98 patches.inspect_getargspec()
99 99
100 100 # Configure the Pylons environment
101 101 config = load_environment(global_conf, app_conf)
102 102
103 103 # The Pylons WSGI app
104 104 app = PylonsApp(config=config)
105 105 if rhodecode.is_test:
106 106 app = csrf.CSRFDetector(app)
107 107
108 108 expected_origin = config.get('expected_origin')
109 109 if expected_origin:
110 110 # The API can be accessed from other Origins.
111 111 app = csrf.OriginChecker(app, expected_origin,
112 112 skip_urls=[routes.util.url_for('api')])
113 113
114 114
115 115 if asbool(full_stack):
116 116
117 117 # Appenlight monitoring and error handler
118 118 app, appenlight_client = wrap_in_appenlight_if_enabled(app, config)
119 119
120 120 # we want our low level middleware to get to the request ASAP. We don't
121 121 # need any pylons stack middleware in them
122 122 app = VCSMiddleware(app, config, appenlight_client)
123 123
124 124 # Establish the Registry for this application
125 125 app = RegistryManager(app)
126 126
127 127 app.config = config
128 128
129 129 return app
130 130
131 131
132 132 def make_pyramid_app(global_config, **settings):
133 133 """
134 134 Constructs the WSGI application based on Pyramid and wraps the Pylons based
135 135 application.
136 136
137 137 Specials:
138 138
139 139 * We migrate from Pylons to Pyramid. While doing this, we keep both
140 140 frameworks functional. This involves moving some WSGI middlewares around
141 141 and providing access to some data internals, so that the old code is
142 142 still functional.
143 143
144 144 * The application can also be integrated like a plugin via the call to
145 145 `includeme`. This is accompanied with the other utility functions which
146 146 are called. Changing this should be done with great care to not break
147 147 cases when these fragments are assembled from another place.
148 148
149 149 """
150 150 # The edition string should be available in pylons too, so we add it here
151 151 # before copying the settings.
152 152 settings.setdefault('rhodecode.edition', 'Community Edition')
153 153
154 154 # As long as our Pylons application does expect "unprepared" settings, make
155 155 # sure that we keep an unmodified copy. This avoids unintentional change of
156 156 # behavior in the old application.
157 157 settings_pylons = settings.copy()
158 158
159 159 sanitize_settings_and_apply_defaults(settings)
160 160 config = Configurator(settings=settings)
161 161 add_pylons_compat_data(config.registry, global_config, settings_pylons)
162 162
163 163 load_pyramid_environment(global_config, settings)
164 164
165 165 includeme_first(config)
166 166 includeme(config)
167 167 pyramid_app = config.make_wsgi_app()
168 168 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
169 169 return pyramid_app
170 170
171 171
172 172 def add_pylons_compat_data(registry, global_config, settings):
173 173 """
174 174 Attach data to the registry to support the Pylons integration.
175 175 """
176 176 registry._pylons_compat_global_config = global_config
177 177 registry._pylons_compat_settings = settings
178 178
179 179
180 180 def webob_to_pyramid_http_response(webob_response):
181 181 ResponseClass = httpexceptions.status_map[webob_response.status_int]
182 182 pyramid_response = ResponseClass(webob_response.status)
183 183 pyramid_response.status = webob_response.status
184 184 pyramid_response.headers.update(webob_response.headers)
185 185 if pyramid_response.headers['content-type'] == 'text/html':
186 186 pyramid_response.headers['content-type'] = 'text/html; charset=UTF-8'
187 187 return pyramid_response
188 188
189 189
190 190 def error_handler(exception, request):
191 191 # TODO: dan: replace the old pylons error controller with this
192 192 from rhodecode.model.settings import SettingsModel
193 193 from rhodecode.lib.utils2 import AttributeDict
194 194
195 195 try:
196 196 rc_config = SettingsModel().get_all_settings()
197 197 except Exception:
198 198 log.exception('failed to fetch settings')
199 199 rc_config = {}
200 200
201 201 base_response = HTTPInternalServerError()
202 202 # prefer original exception for the response since it may have headers set
203 203 if isinstance(exception, HTTPError):
204 204 base_response = exception
205 205
206 206 c = AttributeDict()
207 207 c.error_message = base_response.status
208 208 c.error_explanation = base_response.explanation or str(base_response)
209 209 c.visual = AttributeDict()
210 210
211 211 c.visual.rhodecode_support_url = (
212 212 request.registry.settings.get('rhodecode_support_url') or
213 213 request.route_url('rhodecode_support')
214 214 )
215 215 c.redirect_time = 0
216 216 c.rhodecode_name = rc_config.get('rhodecode_title', '')
217 217 if not c.rhodecode_name:
218 218 c.rhodecode_name = 'Rhodecode'
219 219
220 220 response = render_to_response(
221 221 '/errors/error_document.html', {'c': c}, request=request,
222 222 response=base_response)
223 223
224 224 return response
225 225
226 226
227 227 def includeme(config):
228 228 settings = config.registry.settings
229 229
230 230 if asbool(settings.get('appenlight', 'false')):
231 231 config.include('appenlight_client.ext.pyramid_tween')
232 232
233 233 # Includes which are required. The application would fail without them.
234 234 config.include('pyramid_mako')
235 235 config.include('pyramid_beaker')
236 236 config.include('rhodecode.admin')
237 237 config.include('rhodecode.authentication')
238 238 config.include('rhodecode.integrations')
239 239 config.include('rhodecode.login')
240 240 config.include('rhodecode.tweens')
241 241 config.include('rhodecode.api')
242 242 config.add_route(
243 243 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
244 244
245 245 # Set the authorization policy.
246 246 authz_policy = ACLAuthorizationPolicy()
247 247 config.set_authorization_policy(authz_policy)
248 248
249 249 # Set the default renderer for HTML templates to mako.
250 250 config.add_mako_renderer('.html')
251 251
252 252 # plugin information
253 253 config.registry.rhodecode_plugins = {}
254 254
255 255 config.add_directive(
256 256 'register_rhodecode_plugin', register_rhodecode_plugin)
257 257 # include RhodeCode plugins
258 258 includes = aslist(settings.get('rhodecode.includes', []))
259 259 for inc in includes:
260 260 config.include(inc)
261 261
262 262 pylons_app = make_app(
263 263 config.registry._pylons_compat_global_config,
264 264 **config.registry._pylons_compat_settings)
265 265 config.registry._pylons_compat_config = pylons_app.config
266 266
267 267 pylons_app_as_view = wsgiapp(pylons_app)
268 268
269 269 # Protect from VCS Server error related pages when server is not available
270 270 vcs_server_enabled = asbool(settings.get('vcs.server.enable', 'true'))
271 271 if not vcs_server_enabled:
272 272 pylons_app_as_view = DisableVCSPagesWrapper(pylons_app_as_view)
273 273
274 274
275 275 def pylons_app_with_error_handler(context, request):
276 276 """
277 277 Handle exceptions from rc pylons app:
278 278
279 279 - old webob type exceptions get converted to pyramid exceptions
280 280 - pyramid exceptions are passed to the error handler view
281 281 """
282 282 try:
283 283 response = pylons_app_as_view(context, request)
284 284 if 400 <= response.status_int <= 599: # webob type error responses
285 285 return error_handler(
286 286 webob_to_pyramid_http_response(response), request)
287 287 except HTTPError as e: # pyramid type exceptions
288 288 return error_handler(e, request)
289 289 except Exception:
290 290 if settings.get('debugtoolbar.enabled', False):
291 291 raise
292 292 return error_handler(HTTPInternalServerError(), request)
293 293 return response
294 294
295 295 # This is the glue which allows us to migrate in chunks. By registering the
296 296 # pylons based application as the "Not Found" view in Pyramid, we will
297 297 # fallback to the old application each time the new one does not yet know
298 298 # how to handle a request.
299 299 config.add_notfound_view(pylons_app_with_error_handler)
300 300
301 301 if not settings.get('debugtoolbar.enabled', False):
302 302 # if no toolbar, then any exception gets caught and rendered
303 303 config.add_view(error_handler, context=Exception)
304 304
305 305 config.add_view(error_handler, context=HTTPError)
306 306
307 307
308 308 def includeme_first(config):
309 309 # redirect automatic browser favicon.ico requests to correct place
310 310 def favicon_redirect(context, request):
311 311 return redirect(
312 312 request.static_url('rhodecode:public/images/favicon.ico'))
313 313
314 314 config.add_view(favicon_redirect, route_name='favicon')
315 315 config.add_route('favicon', '/favicon.ico')
316 316
317 config.add_static_view('_static', path='rhodecode:public')
317 config.add_static_view(
318 '_static', path='rhodecode:public', cache_max_age=3600 * 24)
318 319
319 320
320 321 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
321 322 """
322 323 Apply outer WSGI middlewares around the application.
323 324
324 325 Part of this has been moved up from the Pylons layer, so that the
325 326 data is also available if old Pylons code is hit through an already ported
326 327 view.
327 328 """
328 329 settings = config.registry.settings
329 330
330 331 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
331 332 pyramid_app = HttpsFixup(pyramid_app, settings)
332 333
333 334 # Add RoutesMiddleware to support the pylons compatibility tween during
334 335 # migration to pyramid.
335 336 pyramid_app = SkippableRoutesMiddleware(
336 337 pyramid_app, config.registry._pylons_compat_config['routes.map'],
337 338 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
338 339
339 340 if asbool(settings.get('appenlight', 'false')):
340 341 pyramid_app, _ = wrap_in_appenlight_if_enabled(
341 342 pyramid_app, config.registry._pylons_compat_config)
342 343
343 344 if asbool(settings.get('gzip_responses', 'true')):
344 345 pyramid_app = make_gzip_middleware(
345 346 pyramid_app, settings, compress_level=1)
346 347
347 348 return pyramid_app
348 349
349 350
350 351 def sanitize_settings_and_apply_defaults(settings):
351 352 """
352 353 Applies settings defaults and does all type conversion.
353 354
354 355 We would move all settings parsing and preparation into this place, so that
355 356 we have only one place left which deals with this part. The remaining parts
356 357 of the application would start to rely fully on well prepared settings.
357 358
358 359 This piece would later be split up per topic to avoid a big fat monster
359 360 function.
360 361 """
361 362
362 363 # Pyramid's mako renderer has to search in the templates folder so that the
363 364 # old templates still work. Ported and new templates are expected to use
364 365 # real asset specifications for the includes.
365 366 mako_directories = settings.setdefault('mako.directories', [
366 367 # Base templates of the original Pylons application
367 368 'rhodecode:templates',
368 369 ])
369 370 log.debug(
370 371 "Using the following Mako template directories: %s",
371 372 mako_directories)
372 373
373 374 # Default includes, possible to change as a user
374 375 pyramid_includes = settings.setdefault('pyramid.includes', [
375 376 'rhodecode.lib.middleware.request_wrapper',
376 377 ])
377 378 log.debug(
378 379 "Using the following pyramid.includes: %s",
379 380 pyramid_includes)
380 381
381 382 # TODO: johbo: Re-think this, usually the call to config.include
382 383 # should allow to pass in a prefix.
383 384 settings.setdefault('rhodecode.api.url', '/_admin/api')
384 385
385 386 _bool_setting(settings, 'vcs.server.enable', 'true')
386 387 _bool_setting(settings, 'is_test', 'false')
387 388
388 389 return settings
389 390
390 391
391 392 def _bool_setting(settings, name, default):
392 393 settings[name] = asbool(settings.get(name, default))
General Comments 0
You need to be logged in to leave comments. Login now