##// END OF EJS Templates
middleware: add the register plugin directive further up the config stack
ergo -
r470:d29f1a26 default
parent child Browse files
Show More
@@ -1,393 +1,394 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 # plugin information
231 config.registry.rhodecode_plugins = {}
232
233 config.add_directive(
234 'register_rhodecode_plugin', register_rhodecode_plugin)
235
230 236 if asbool(settings.get('appenlight', 'false')):
231 237 config.include('appenlight_client.ext.pyramid_tween')
232 238
233 239 # Includes which are required. The application would fail without them.
234 240 config.include('pyramid_mako')
235 241 config.include('pyramid_beaker')
236 242 config.include('rhodecode.admin')
237 243 config.include('rhodecode.authentication')
238 244 config.include('rhodecode.integrations')
239 245 config.include('rhodecode.login')
240 246 config.include('rhodecode.tweens')
241 247 config.include('rhodecode.api')
242 248 config.add_route(
243 249 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
244 250
245 251 # Set the authorization policy.
246 252 authz_policy = ACLAuthorizationPolicy()
247 253 config.set_authorization_policy(authz_policy)
248 254
249 255 # Set the default renderer for HTML templates to mako.
250 256 config.add_mako_renderer('.html')
251 257
252 # plugin information
253 config.registry.rhodecode_plugins = {}
254
255 config.add_directive(
256 'register_rhodecode_plugin', register_rhodecode_plugin)
257 258 # include RhodeCode plugins
258 259 includes = aslist(settings.get('rhodecode.includes', []))
259 260 for inc in includes:
260 261 config.include(inc)
261 262
262 263 pylons_app = make_app(
263 264 config.registry._pylons_compat_global_config,
264 265 **config.registry._pylons_compat_settings)
265 266 config.registry._pylons_compat_config = pylons_app.config
266 267
267 268 pylons_app_as_view = wsgiapp(pylons_app)
268 269
269 270 # Protect from VCS Server error related pages when server is not available
270 271 vcs_server_enabled = asbool(settings.get('vcs.server.enable', 'true'))
271 272 if not vcs_server_enabled:
272 273 pylons_app_as_view = DisableVCSPagesWrapper(pylons_app_as_view)
273 274
274 275
275 276 def pylons_app_with_error_handler(context, request):
276 277 """
277 278 Handle exceptions from rc pylons app:
278 279
279 280 - old webob type exceptions get converted to pyramid exceptions
280 281 - pyramid exceptions are passed to the error handler view
281 282 """
282 283 try:
283 284 response = pylons_app_as_view(context, request)
284 285 if 400 <= response.status_int <= 599: # webob type error responses
285 286 return error_handler(
286 287 webob_to_pyramid_http_response(response), request)
287 288 except HTTPError as e: # pyramid type exceptions
288 289 return error_handler(e, request)
289 290 except Exception:
290 291 if settings.get('debugtoolbar.enabled', False):
291 292 raise
292 293 return error_handler(HTTPInternalServerError(), request)
293 294 return response
294 295
295 296 # This is the glue which allows us to migrate in chunks. By registering the
296 297 # pylons based application as the "Not Found" view in Pyramid, we will
297 298 # fallback to the old application each time the new one does not yet know
298 299 # how to handle a request.
299 300 config.add_notfound_view(pylons_app_with_error_handler)
300 301
301 302 if not settings.get('debugtoolbar.enabled', False):
302 303 # if no toolbar, then any exception gets caught and rendered
303 304 config.add_view(error_handler, context=Exception)
304 305
305 306 config.add_view(error_handler, context=HTTPError)
306 307
307 308
308 309 def includeme_first(config):
309 310 # redirect automatic browser favicon.ico requests to correct place
310 311 def favicon_redirect(context, request):
311 312 return redirect(
312 313 request.static_url('rhodecode:public/images/favicon.ico'))
313 314
314 315 config.add_view(favicon_redirect, route_name='favicon')
315 316 config.add_route('favicon', '/favicon.ico')
316 317
317 318 config.add_static_view(
318 319 '_static', path='rhodecode:public', cache_max_age=3600 * 24)
319 320
320 321
321 322 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
322 323 """
323 324 Apply outer WSGI middlewares around the application.
324 325
325 326 Part of this has been moved up from the Pylons layer, so that the
326 327 data is also available if old Pylons code is hit through an already ported
327 328 view.
328 329 """
329 330 settings = config.registry.settings
330 331
331 332 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
332 333 pyramid_app = HttpsFixup(pyramid_app, settings)
333 334
334 335 # Add RoutesMiddleware to support the pylons compatibility tween during
335 336 # migration to pyramid.
336 337 pyramid_app = SkippableRoutesMiddleware(
337 338 pyramid_app, config.registry._pylons_compat_config['routes.map'],
338 339 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
339 340
340 341 if asbool(settings.get('appenlight', 'false')):
341 342 pyramid_app, _ = wrap_in_appenlight_if_enabled(
342 343 pyramid_app, config.registry._pylons_compat_config)
343 344
344 345 if asbool(settings.get('gzip_responses', 'true')):
345 346 pyramid_app = make_gzip_middleware(
346 347 pyramid_app, settings, compress_level=1)
347 348
348 349 return pyramid_app
349 350
350 351
351 352 def sanitize_settings_and_apply_defaults(settings):
352 353 """
353 354 Applies settings defaults and does all type conversion.
354 355
355 356 We would move all settings parsing and preparation into this place, so that
356 357 we have only one place left which deals with this part. The remaining parts
357 358 of the application would start to rely fully on well prepared settings.
358 359
359 360 This piece would later be split up per topic to avoid a big fat monster
360 361 function.
361 362 """
362 363
363 364 # Pyramid's mako renderer has to search in the templates folder so that the
364 365 # old templates still work. Ported and new templates are expected to use
365 366 # real asset specifications for the includes.
366 367 mako_directories = settings.setdefault('mako.directories', [
367 368 # Base templates of the original Pylons application
368 369 'rhodecode:templates',
369 370 ])
370 371 log.debug(
371 372 "Using the following Mako template directories: %s",
372 373 mako_directories)
373 374
374 375 # Default includes, possible to change as a user
375 376 pyramid_includes = settings.setdefault('pyramid.includes', [
376 377 'rhodecode.lib.middleware.request_wrapper',
377 378 ])
378 379 log.debug(
379 380 "Using the following pyramid.includes: %s",
380 381 pyramid_includes)
381 382
382 383 # TODO: johbo: Re-think this, usually the call to config.include
383 384 # should allow to pass in a prefix.
384 385 settings.setdefault('rhodecode.api.url', '/_admin/api')
385 386
386 387 _bool_setting(settings, 'vcs.server.enable', 'true')
387 388 _bool_setting(settings, 'is_test', 'false')
388 389
389 390 return settings
390 391
391 392
392 393 def _bool_setting(settings, name, default):
393 394 settings[name] = asbool(settings.get(name, default))
General Comments 0
You need to be logged in to leave comments. Login now