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