Skip to content
Permalink
dbe9fd8d22
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
289 lines (237 sloc) 9.67 KB
# encoding: utf-8
import os
import re
from pylons.wsgiapp import PylonsApp
from beaker.middleware import CacheMiddleware, SessionMiddleware
from paste.cascade import Cascade
from paste.registry import RegistryManager
from paste.urlparser import StaticURLParser
from ckan.common import asbool
from paste.fileapp import _FileIter
from pylons.middleware import ErrorHandler, StatusCodeRedirect
from routes.middleware import RoutesMiddleware
from repoze.who.config import WhoConfig
from repoze.who.middleware import PluggableAuthenticationMiddleware
from fanstatic import Fanstatic
from ckan.plugins import PluginImplementations
from ckan.plugins.interfaces import IMiddleware
import ckan.lib.uploader as uploader
from ckan.config.middleware import common_middleware
from ckan.common import config
import logging
log = logging.getLogger(__name__)
def make_pylons_stack(conf, full_stack=True, static_files=True,
**app_conf):
"""Create a Pylons WSGI application and return it
``conf``
The inherited configuration for this application. Normally from
the [DEFAULT] section of the Paste ini file.
``full_stack``
Whether 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.
``static_files``
Whether this application serves its own static files; disable
when another web server is responsible for serving them.
``app_conf``
The application's local configuration. Normally specified in
the [app:<name>] section of the Paste ini file (where <name>
defaults to main).
"""
# The Pylons WSGI app
app = pylons_app = CKANPylonsApp()
for plugin in PluginImplementations(IMiddleware):
app = plugin.make_middleware(app, config)
app = common_middleware.CloseWSGIInputMiddleware(app, config)
app = common_middleware.RootPathMiddleware(app, config)
# Routing/Session/Cache Middleware
app = RoutesMiddleware(app, config['routes.map'])
# we want to be able to retrieve the routes middleware to be able to update
# the mapper. We store it in the pylons config to allow this.
config['routes.middleware'] = app
app = SessionMiddleware(app, config)
app = CacheMiddleware(app, config)
# CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
# app = QueueLogMiddleware(app)
if asbool(config.get('ckan.use_pylons_response_cleanup_middleware',
True)):
app = execute_on_completion(app, config,
cleanup_pylons_response_string)
# Fanstatic
fanstatic_enable_rollup = asbool(
conf.get('fanstatic_enable_rollup', False))
if asbool(config.get('debug', False)):
fanstatic_config = {
'versioning': True,
'recompute_hashes': True,
'minified': False,
'bottom': True,
'bundle': False,
'rollup': fanstatic_enable_rollup,
}
else:
fanstatic_config = {
'versioning': True,
'recompute_hashes': False,
'minified': True,
'bottom': True,
'bundle': True,
'rollup': fanstatic_enable_rollup,
}
root_path = config.get('ckan.root_path', None)
if root_path:
root_path = re.sub('/{{LANG}}', '', root_path)
fanstatic_config['base_url'] = root_path
app = Fanstatic(app, **fanstatic_config)
for plugin in PluginImplementations(IMiddleware):
try:
app = plugin.make_error_log_middleware(app, config)
except AttributeError:
log.critical('Middleware class {0} is missing the method'
'make_error_log_middleware.'
.format(plugin.__class__.__name__))
if asbool(full_stack):
# Handle Python exceptions
app = ErrorHandler(app, conf, **config['pylons.errorware'])
# Display error documents for 400, 403, 404 status codes (and
# 500 when debug is disabled)
if asbool(config['debug']):
app = StatusCodeRedirect(app, [400, 403, 404])
else:
app = StatusCodeRedirect(app, [400, 403, 404, 500])
# Initialize repoze.who
who_parser = WhoConfig(conf['here'])
who_parser.parse(open(conf['who.config_file']))
app = PluggableAuthenticationMiddleware(
app,
who_parser.identifiers,
who_parser.authenticators,
who_parser.challengers,
who_parser.mdproviders,
who_parser.request_classifier,
who_parser.challenge_decider,
logging.getLogger('repoze.who'),
logging.WARN, # ignored
who_parser.remote_user_key
)
# Establish the Registry for this application
app = RegistryManager(app, streaming=False)
if asbool(static_files):
# Serve static files
static_max_age = None if not asbool(
config.get('ckan.cache_enabled')) \
else int(config.get('ckan.static_max_age', 3600))
static_app = StaticURLParser(
config['pylons.paths']['static_files'],
cache_max_age=static_max_age)
static_parsers = [static_app, app]
storage_directory = uploader.get_storage_path()
if storage_directory:
path = os.path.join(storage_directory, 'storage')
try:
os.makedirs(path)
except OSError as e:
# errno 17 is file already exists
if e.errno != 17:
raise
storage_app = StaticURLParser(path, cache_max_age=static_max_age)
static_parsers.insert(0, storage_app)
# Configurable extra static file paths
extra_static_parsers = []
for public_path in config.get(
'extra_public_paths', '').split(','):
if public_path.strip():
extra_static_parsers.append(
StaticURLParser(public_path.strip(),
cache_max_age=static_max_age)
)
app = Cascade(extra_static_parsers + static_parsers)
# Prevent the host from request to be added to the new header location.
app = common_middleware.HostHeaderMiddleware(app)
# Tracking
if asbool(config.get('ckan.tracking_enabled', 'false')):
app = common_middleware.TrackingMiddleware(app, config)
# Add a reference to the actual Pylons app so it's easier to access
app._wsgi_app = pylons_app
return app
class CKANPylonsApp(PylonsApp):
app_name = 'pylons_app'
def can_handle_request(self, environ):
'''
Decides whether it can handle a request with the Pylons app by
matching the request environ against the route mapper
Returns (True, 'pylons_app', origin) if this is the case.
origin can be either 'core' or 'extension' depending on where
the route was defined.
NOTE: There is currently a catch all route for GET requests to
point arbitrary urls to templates with the same name:
map.connect('/*url', controller='template', action='view')
This means that this function will match all GET requests. This
does not cause issues as the Pylons core routes are the last to
take precedence so the current behaviour is kept, but it's worth
keeping in mind.
'''
pylons_mapper = config['routes.map']
match_route = pylons_mapper.routematch(environ=environ)
if match_route:
match, route = match_route
origin = 'core'
if hasattr(route, '_ckan_core') and not route._ckan_core:
origin = 'extension'
log.debug('Pylons route match: {0} Origin: {1}'.format(
match, origin))
return (True, self.app_name, origin)
else:
return (False, self.app_name)
class CloseCallbackWrapper(object):
def __init__(self, iterable, callback, environ):
# pylons.fileapp expects app_iter to have `file` attribute.
self.file = iterable
self.callback = callback
self.environ = environ
def __iter__(self):
"""
return a generator that passes through items from iterable
then calls callback(environ).
"""
try:
for item in self.file:
yield item
except GeneratorExit:
if hasattr(self.file, 'close'):
self.file.close()
raise
finally:
self.callback(self.environ)
class FileIterWrapper(CloseCallbackWrapper, _FileIter):
"""Same CloseCallbackWrapper, just with _FileIter mixin.
That will prevent pylons from converting file responses into
in-memori lists.
"""
pass
def execute_on_completion(application, config, callback):
"""
Call callback(environ) once complete response is sent
"""
def inner(environ, start_response):
try:
result = application(environ, start_response)
except Exception:
callback(environ)
raise
# paste.fileapp converts non-file responses into list
# In order to avoid interception of OOM Killer
# file responses wrapped into generator with
# _FileIter in parent tree.
klass = CloseCallbackWrapper
if isinstance(result, _FileIter):
klass = FileIterWrapper
return klass(result, callback, environ)
return inner
def cleanup_pylons_response_string(environ):
try:
msg = 'response cleared by pylons response cleanup middleware'
environ['pylons.controller']._py_object.response._body = msg
except (KeyError, AttributeError):
pass