##// END OF EJS Templates
frontend: prepare for plugin registration and life-cycle handling
frontend: prepare for plugin registration and life-cycle handling

File last commit:

r210:57b11684 merge default
r373:379025d6 default
Show More
middleware.py
387 lines | 14.0 KiB | text/x-python | PythonLexer
project: added all source files and assets
r1 # -*- coding: utf-8 -*-
# Copyright (C) 2010-2016 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
"""
Pylons middleware initialization
"""
import logging
from paste.registry import RegistryManager
from paste.gzipper import make_gzip_middleware
from pylons.wsgiapp import PylonsApp
from pyramid.authorization import ACLAuthorizationPolicy
from pyramid.config import Configurator
from pyramid.static import static_view
from pyramid.settings import asbool, aslist
from pyramid.wsgi import wsgiapp
dan
errorpages: use original http status for rendered error page
r190 from pyramid.httpexceptions import HTTPError, HTTPInternalServerError
dan
pyramid: make responses/exceptions from pyramid/pylons work
r187 import pyramid.httpexceptions as httpexceptions
dan
errorpages: use original http status for rendered error page
r190 from pyramid.renderers import render_to_response, render
project: added all source files and assets
r1 from routes.middleware import RoutesMiddleware
import routes.util
import rhodecode
db: Move db setup code to seperate function.
r121 from rhodecode.config import patches
from rhodecode.config.environment import (
load_environment, load_pyramid_environment)
project: added all source files and assets
r1 from rhodecode.lib.middleware import csrf
from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
from rhodecode.lib.middleware.disable_vcs import DisableVCSPagesWrapper
from rhodecode.lib.middleware.https_fixup import HttpsFixup
from rhodecode.lib.middleware.vcs import VCSMiddleware
from rhodecode.lib.plugins.utils import register_rhodecode_plugin
log = logging.getLogger(__name__)
def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
"""Create a Pylons WSGI application and return it
``global_conf``
The inherited configuration for this application. Normally from
the [DEFAULT] section of the Paste ini file.
``full_stack``
Whether or not this application provides a full WSGI stack (by
default, meaning it handles its own exceptions and errors).
Disable full_stack when this application is "managed" by
another WSGI middleware.
``app_conf``
The application's local configuration. Normally specified in
the [app:<name>] section of the Paste ini file (where <name>
defaults to main).
"""
# Apply compatibility patches
patches.kombu_1_5_1_python_2_7_11()
patches.inspect_getargspec()
# Configure the Pylons environment
config = load_environment(global_conf, app_conf)
# The Pylons WSGI app
app = PylonsApp(config=config)
if rhodecode.is_test:
app = csrf.CSRFDetector(app)
expected_origin = config.get('expected_origin')
if expected_origin:
# The API can be accessed from other Origins.
app = csrf.OriginChecker(app, expected_origin,
skip_urls=[routes.util.url_for('api')])
if asbool(full_stack):
# Appenlight monitoring and error handler
app, appenlight_client = wrap_in_appenlight_if_enabled(app, config)
# we want our low level middleware to get to the request ASAP. We don't
# need any pylons stack middleware in them
app = VCSMiddleware(app, config, appenlight_client)
# Establish the Registry for this application
app = RegistryManager(app)
app.config = config
return app
def make_pyramid_app(global_config, **settings):
"""
Constructs the WSGI application based on Pyramid and wraps the Pylons based
application.
Specials:
* We migrate from Pylons to Pyramid. While doing this, we keep both
frameworks functional. This involves moving some WSGI middlewares around
and providing access to some data internals, so that the old code is
still functional.
* The application can also be integrated like a plugin via the call to
`includeme`. This is accompanied with the other utility functions which
are called. Changing this should be done with great care to not break
cases when these fragments are assembled from another place.
"""
# The edition string should be available in pylons too, so we add it here
# before copying the settings.
settings.setdefault('rhodecode.edition', 'Community Edition')
# As long as our Pylons application does expect "unprepared" settings, make
# sure that we keep an unmodified copy. This avoids unintentional change of
# behavior in the old application.
settings_pylons = settings.copy()
sanitize_settings_and_apply_defaults(settings)
config = Configurator(settings=settings)
add_pylons_compat_data(config.registry, global_config, settings_pylons)
db: Move initialization of test environment up to pyramid layer.
r116
db: Move db setup code to seperate function.
r121 load_pyramid_environment(global_config, settings)
db: Move initialization of test environment up to pyramid layer.
r116
project: added all source files and assets
r1 includeme(config)
includeme_last(config)
pyramid_app = config.make_wsgi_app()
pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
return pyramid_app
def add_pylons_compat_data(registry, global_config, settings):
"""
Attach data to the registry to support the Pylons integration.
"""
registry._pylons_compat_global_config = global_config
registry._pylons_compat_settings = settings
dan
errorpages: convert webob responses better, include status text + headers
r191 def webob_to_pyramid_http_response(webob_response):
ResponseClass = httpexceptions.status_map[webob_response.status_int]
pyramid_response = ResponseClass(webob_response.status)
pyramid_response.status = webob_response.status
pyramid_response.headers.update(webob_response.headers)
if pyramid_response.headers['content-type'] == 'text/html':
pyramid_response.headers['content-type'] = 'text/html; charset=UTF-8'
return pyramid_response
dan
errorpages: add appenlight to pyramid layer
r194 def error_handler(exception, request):
dan
pyramid: make responses/exceptions from pyramid/pylons work
r187 # TODO: dan: replace the old pylons error controller with this
from rhodecode.model.settings import SettingsModel
from rhodecode.lib.utils2 import AttributeDict
try:
rc_config = SettingsModel().get_all_settings()
except Exception:
log.exception('failed to fetch settings')
rc_config = {}
dan
errorpages: add appenlight to pyramid layer
r194 base_response = HTTPInternalServerError()
# prefer original exception for the response since it may have headers set
if isinstance(exception, HTTPError):
base_response = exception
dan
pyramid: make responses/exceptions from pyramid/pylons work
r187 c = AttributeDict()
dan
errorpages: add appenlight to pyramid layer
r194 c.error_message = base_response.status
c.error_explanation = base_response.explanation or str(base_response)
dan
pyramid: make responses/exceptions from pyramid/pylons work
r187 c.visual = AttributeDict()
c.visual.rhodecode_support_url = (
request.registry.settings.get('rhodecode_support_url') or
request.route_url('rhodecode_support')
)
c.redirect_time = 0
dan
errorpages: add appenlight to pyramid layer
r194 c.rhodecode_name = rc_config.get('rhodecode_title', '')
dan
pyramid: make responses/exceptions from pyramid/pylons work
r187 if not c.rhodecode_name:
c.rhodecode_name = 'Rhodecode'
response = render_to_response(
dan
errorpages: use original http status for rendered error page
r190 '/errors/error_document.html', {'c': c}, request=request,
response=base_response)
dan
pyramid: make responses/exceptions from pyramid/pylons work
r187 return response
project: added all source files and assets
r1 def includeme(config):
settings = config.registry.settings
dan
errorpages: add appenlight to pyramid layer
r194 if asbool(settings.get('appenlight', 'false')):
config.include('appenlight_client.ext.pyramid_tween')
project: added all source files and assets
r1 # Includes which are required. The application would fail without them.
config.include('pyramid_mako')
config.include('pyramid_beaker')
Martin Bornhold
oss-licenses: Migrate view to pyramid.
r204 config.include('rhodecode.admin')
project: added all source files and assets
r1 config.include('rhodecode.authentication')
login: Include login configuration.
r36 config.include('rhodecode.login')
project: added all source files and assets
r1 config.include('rhodecode.tweens')
config.include('rhodecode.api')
dan
pyramid: make responses/exceptions from pyramid/pylons work
r187 config.add_route(
'rhodecode_support', 'https://rhodecode.com/help/', static=True)
project: added all source files and assets
r1
# Set the authorization policy.
authz_policy = ACLAuthorizationPolicy()
config.set_authorization_policy(authz_policy)
# Set the default renderer for HTML templates to mako.
config.add_mako_renderer('.html')
# plugin information
config.registry.rhodecode_plugins = {}
config.add_directive(
'register_rhodecode_plugin', register_rhodecode_plugin)
# include RhodeCode plugins
includes = aslist(settings.get('rhodecode.includes', []))
for inc in includes:
config.include(inc)
dan
pyramid: make responses/exceptions from pyramid/pylons work
r187 pylons_app = make_app(
config.registry._pylons_compat_global_config,
**config.registry._pylons_compat_settings)
config.registry._pylons_compat_config = pylons_app.config
pylons_app_as_view = wsgiapp(pylons_app)
# Protect from VCS Server error related pages when server is not available
vcs_server_enabled = asbool(settings.get('vcs.server.enable', 'true'))
if not vcs_server_enabled:
pylons_app_as_view = DisableVCSPagesWrapper(pylons_app_as_view)
dan
errorpages: add appenlight to pyramid layer
r194
dan
pyramid: make responses/exceptions from pyramid/pylons work
r187 def pylons_app_with_error_handler(context, request):
"""
Handle exceptions from rc pylons app:
- old webob type exceptions get converted to pyramid exceptions
- pyramid exceptions are passed to the error handler view
"""
try:
response = pylons_app_as_view(context, request)
if 400 <= response.status_int <= 599: # webob type error responses
dan
errorpages: convert webob responses better, include status text + headers
r191 return error_handler(
webob_to_pyramid_http_response(response), request)
dan
pyramid: make responses/exceptions from pyramid/pylons work
r187 except HTTPError as e: # pyramid type exceptions
return error_handler(e, request)
dan
errorpages: convert general exceptions to error page if toolbar disabled
r193 except Exception:
if settings.get('debugtoolbar.enabled', False):
raise
return error_handler(HTTPInternalServerError(), request)
dan
pyramid: make responses/exceptions from pyramid/pylons work
r187 return response
project: added all source files and assets
r1 # This is the glue which allows us to migrate in chunks. By registering the
# pylons based application as the "Not Found" view in Pyramid, we will
# fallback to the old application each time the new one does not yet know
# how to handle a request.
dan
pyramid: make responses/exceptions from pyramid/pylons work
r187 config.add_notfound_view(pylons_app_with_error_handler)
project: added all source files and assets
r1
dan
errorpages: add appenlight to pyramid layer
r194 if settings.get('debugtoolbar.enabled', False):
# if toolbar, then only http type exceptions get caught and rendered
ExcClass = HTTPError
else:
# if no toolbar, then any exception gets caught and rendered
ExcClass = Exception
config.add_view(error_handler, context=ExcClass)
project: added all source files and assets
r1
def includeme_last(config):
"""
The static file catchall needs to be last in the view configuration.
"""
settings = config.registry.settings
# Note: johbo: I would prefer to register a prefix for static files at some
# point, e.g. move them under '_static/'. This would fully avoid that we
# can have name clashes with a repository name. Imaging someone calling his
# repo "css" ;-) Also having an external web server to serve out the static
# files seems to be easier to set up if they have a common prefix.
#
# Example: config.add_static_view('_static', path='rhodecode:public')
#
# It might be an option to register both paths for a while and then migrate
# over to the new location.
# Serving static files with a catchall.
if settings['static_files']:
config.add_route('catchall_static', '/*subpath')
config.add_view(
static_view('rhodecode:public'), route_name='catchall_static')
def wrap_app_in_wsgi_middlewares(pyramid_app, config):
"""
Apply outer WSGI middlewares around the application.
Part of this has been moved up from the Pylons layer, so that the
data is also available if old Pylons code is hit through an already ported
view.
"""
settings = config.registry.settings
config: Move HttpsFixup middleware up...
r181 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
pyramid_app = HttpsFixup(pyramid_app, settings)
dan
pyramid: make responses/exceptions from pyramid/pylons work
r187 # Add RoutesMiddleware to support the pylons compatibility tween during
config: Move HttpsFixup middleware up...
r181
project: added all source files and assets
r1 # migration to pyramid.
pyramid_app = RoutesMiddleware(
pyramid_app, config.registry._pylons_compat_config['routes.map'])
dan
errorpages: add appenlight to pyramid layer
r194 if asbool(settings.get('appenlight', 'false')):
pyramid_app, _ = wrap_in_appenlight_if_enabled(
pyramid_app, config.registry._pylons_compat_config)
project: added all source files and assets
r1 # TODO: johbo: Don't really see why we enable the gzip middleware when
# serving static files, might be something that should have its own setting
# as well?
if settings['static_files']:
pyramid_app = make_gzip_middleware(
pyramid_app, settings, compress_level=1)
return pyramid_app
def sanitize_settings_and_apply_defaults(settings):
"""
Applies settings defaults and does all type conversion.
We would move all settings parsing and preparation into this place, so that
we have only one place left which deals with this part. The remaining parts
of the application would start to rely fully on well prepared settings.
This piece would later be split up per topic to avoid a big fat monster
function.
"""
# Pyramid's mako renderer has to search in the templates folder so that the
# old templates still work. Ported and new templates are expected to use
# real asset specifications for the includes.
mako_directories = settings.setdefault('mako.directories', [
# Base templates of the original Pylons application
'rhodecode:templates',
])
log.debug(
"Using the following Mako template directories: %s",
mako_directories)
# Default includes, possible to change as a user
pyramid_includes = settings.setdefault('pyramid.includes', [
'rhodecode.lib.middleware.request_wrapper',
])
log.debug(
"Using the following pyramid.includes: %s",
pyramid_includes)
# TODO: johbo: Re-think this, usually the call to config.include
# should allow to pass in a prefix.
settings.setdefault('rhodecode.api.url', '/_admin/api')
_bool_setting(settings, 'vcs.server.enable', 'true')
_bool_setting(settings, 'static_files', 'true')
db: Set `rhodecode.is_test` in `make_pyramid_app` instead of `make_app`...
r118 _bool_setting(settings, 'is_test', 'false')
project: added all source files and assets
r1
return settings
def _bool_setting(settings, name, default):
settings[name] = asbool(settings.get(name, default))