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