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