summaryrefslogtreecommitdiff
path: root/paste/deploy/config.py
diff options
context:
space:
mode:
Diffstat (limited to 'paste/deploy/config.py')
-rw-r--r--paste/deploy/config.py305
1 files changed, 305 insertions, 0 deletions
diff --git a/paste/deploy/config.py b/paste/deploy/config.py
new file mode 100644
index 0000000..a503007
--- /dev/null
+++ b/paste/deploy/config.py
@@ -0,0 +1,305 @@
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+"""Paste Configuration Middleware and Objects"""
+import threading
+import re
+
+# Loaded lazily
+wsgilib = None
+local = None
+
+__all__ = ['DispatchingConfig', 'CONFIG', 'ConfigMiddleware', 'PrefixMiddleware']
+
+
+def local_dict():
+ global config_local, local
+ try:
+ return config_local.wsgi_dict
+ except NameError:
+ config_local = threading.local()
+ config_local.wsgi_dict = result = {}
+ return result
+ except AttributeError:
+ config_local.wsgi_dict = result = {}
+ return result
+
+
+class DispatchingConfig(object):
+
+ """
+ This is a configuration object that can be used globally,
+ imported, have references held onto. The configuration may differ
+ by thread (or may not).
+
+ Specific configurations are registered (and deregistered) either
+ for the process or for threads.
+ """
+
+ # @@: What should happen when someone tries to add this
+ # configuration to itself? Probably the conf should become
+ # resolved, and get rid of this delegation wrapper
+
+ _constructor_lock = threading.Lock()
+
+ def __init__(self):
+ self._constructor_lock.acquire()
+ try:
+ self.dispatching_id = 0
+ while 1:
+ self._local_key = 'paste.processconfig_%i' % self.dispatching_id
+ if not self._local_key in local_dict():
+ break
+ self.dispatching_id += 1
+ finally:
+ self._constructor_lock.release()
+ self._process_configs = []
+
+ def push_thread_config(self, conf):
+ """
+ Make ``conf`` the active configuration for this thread.
+ Thread-local configuration always overrides process-wide
+ configuration.
+
+ This should be used like::
+
+ conf = make_conf()
+ dispatching_config.push_thread_config(conf)
+ try:
+ ... do stuff ...
+ finally:
+ dispatching_config.pop_thread_config(conf)
+ """
+ local_dict().setdefault(self._local_key, []).append(conf)
+
+ def pop_thread_config(self, conf=None):
+ """
+ Remove a thread-local configuration. If ``conf`` is given,
+ it is checked against the popped configuration and an error
+ is emitted if they don't match.
+ """
+ self._pop_from(local_dict()[self._local_key], conf)
+
+ def _pop_from(self, lst, conf):
+ popped = lst.pop()
+ if conf is not None and popped is not conf:
+ raise AssertionError(
+ "The config popped (%s) is not the same as the config "
+ "expected (%s)"
+ % (popped, conf))
+
+ def push_process_config(self, conf):
+ """
+ Like push_thread_config, but applies the configuration to
+ the entire process.
+ """
+ self._process_configs.append(conf)
+
+ def pop_process_config(self, conf=None):
+ self._pop_from(self._process_configs, conf)
+
+ def __getattr__(self, attr):
+ conf = self.current_conf()
+ if conf is None:
+ raise AttributeError(
+ "No configuration has been registered for this process "
+ "or thread")
+ return getattr(conf, attr)
+
+ def current_conf(self):
+ thread_configs = local_dict().get(self._local_key)
+ if thread_configs:
+ return thread_configs[-1]
+ elif self._process_configs:
+ return self._process_configs[-1]
+ else:
+ return None
+
+ def __getitem__(self, key):
+ # I thought __getattr__ would catch this, but apparently not
+ conf = self.current_conf()
+ if conf is None:
+ raise TypeError(
+ "No configuration has been registered for this process "
+ "or thread")
+ return conf[key]
+
+ def __contains__(self, key):
+ # I thought __getattr__ would catch this, but apparently not
+ return key in self
+
+ def __setitem__(self, key, value):
+ # I thought __getattr__ would catch this, but apparently not
+ conf = self.current_conf()
+ conf[key] = value
+
+CONFIG = DispatchingConfig()
+
+
+class ConfigMiddleware(object):
+
+ """
+ A WSGI middleware that adds a ``paste.config`` key to the request
+ environment, as well as registering the configuration temporarily
+ (for the length of the request) with ``paste.CONFIG``.
+ """
+
+ def __init__(self, application, config):
+ """
+ This delegates all requests to `application`, adding a *copy*
+ of the configuration `config`.
+ """
+ self.application = application
+ self.config = config
+
+ def __call__(self, environ, start_response):
+ global wsgilib
+ if wsgilib is None:
+ import pkg_resources
+ pkg_resources.require('Paste')
+ from paste import wsgilib
+ popped_config = None
+ if 'paste.config' in environ:
+ popped_config = environ['paste.config']
+ conf = environ['paste.config'] = self.config.copy()
+ app_iter = None
+ CONFIG.push_thread_config(conf)
+ try:
+ app_iter = self.application(environ, start_response)
+ finally:
+ if app_iter is None:
+ # An error occurred...
+ CONFIG.pop_thread_config(conf)
+ if popped_config is not None:
+ environ['paste.config'] = popped_config
+ if type(app_iter) in (list, tuple):
+ # Because it is a concrete iterator (not a generator) we
+ # know the configuration for this thread is no longer
+ # needed:
+ CONFIG.pop_thread_config(conf)
+ if popped_config is not None:
+ environ['paste.config'] = popped_config
+ return app_iter
+ else:
+ def close_config():
+ CONFIG.pop_thread_config(conf)
+ new_app_iter = wsgilib.add_close(app_iter, close_config)
+ return new_app_iter
+
+
+def make_config_filter(app, global_conf, **local_conf):
+ conf = global_conf.copy()
+ conf.update(local_conf)
+ return ConfigMiddleware(app, conf)
+
+make_config_middleware = ConfigMiddleware.__doc__
+
+
+class PrefixMiddleware(object):
+ """Translate a given prefix into a SCRIPT_NAME for the filtered
+ application.
+
+ PrefixMiddleware provides a way to manually override the root prefix
+ (SCRIPT_NAME) of your application for certain, rare situations.
+
+ When running an application under a prefix (such as '/james') in
+ FastCGI/apache, the SCRIPT_NAME environment variable is automatically
+ set to to the appropriate value: '/james'. Pylons' URL generating
+ functions, such as url_for, always take the SCRIPT_NAME value into account.
+
+ One situation where PrefixMiddleware is required is when an application
+ is accessed via a reverse proxy with a prefix. The application is accessed
+ through the reverse proxy via the the URL prefix '/james', whereas the
+ reverse proxy forwards those requests to the application at the prefix '/'.
+
+ The reverse proxy, being an entirely separate web server, has no way of
+ specifying the SCRIPT_NAME variable; it must be manually set by a
+ PrefixMiddleware instance. Without setting SCRIPT_NAME, url_for will
+ generate URLs such as: '/purchase_orders/1', when it should be
+ generating: '/james/purchase_orders/1'.
+
+ To filter your application through a PrefixMiddleware instance, add the
+ following to the '[app:main]' section of your .ini file:
+
+ .. code-block:: ini
+
+ filter-with = proxy-prefix
+
+ [filter:proxy-prefix]
+ use = egg:PasteDeploy#prefix
+ prefix = /james
+
+ The name ``proxy-prefix`` simply acts as an identifier of the filter
+ section; feel free to rename it.
+
+ Also, unless disabled, the ``X-Forwarded-Server`` header will be
+ translated to the ``Host`` header, for cases when that header is
+ lost in the proxying. Also ``X-Forwarded-Host``,
+ ``X-Forwarded-Scheme``, and ``X-Forwarded-Proto`` are translated.
+
+ If ``force_port`` is set, SERVER_PORT and HTTP_HOST will be
+ rewritten with the given port. You can use a number, string (like
+ '80') or the empty string (whatever is the default port for the
+ scheme). This is useful in situations where there is port
+ forwarding going on, and the server believes itself to be on a
+ different port than what the outside world sees.
+
+ You can also use ``scheme`` to explicitly set the scheme (like
+ ``scheme = https``).
+ """
+ def __init__(self, app, global_conf=None, prefix='/',
+ translate_forwarded_server=True,
+ force_port=None, scheme=None):
+ self.app = app
+ self.prefix = prefix.rstrip('/')
+ self.translate_forwarded_server = translate_forwarded_server
+ self.regprefix = re.compile("^%s(.*)$" % self.prefix)
+ self.force_port = force_port
+ self.scheme = scheme
+
+ def __call__(self, environ, start_response):
+ url = environ['PATH_INFO']
+ url = re.sub(self.regprefix, r'\1', url)
+ if not url:
+ url = '/'
+ environ['PATH_INFO'] = url
+ environ['SCRIPT_NAME'] = self.prefix
+ if self.translate_forwarded_server:
+ if 'HTTP_X_FORWARDED_SERVER' in environ:
+ environ['SERVER_NAME'] = environ['HTTP_HOST'] = environ.pop('HTTP_X_FORWARDED_SERVER').split(',')[0]
+ if 'HTTP_X_FORWARDED_HOST' in environ:
+ environ['HTTP_HOST'] = environ.pop('HTTP_X_FORWARDED_HOST').split(',')[0]
+ if 'HTTP_X_FORWARDED_FOR' in environ:
+ environ['REMOTE_ADDR'] = environ.pop('HTTP_X_FORWARDED_FOR').split(',')[0]
+ if 'HTTP_X_FORWARDED_SCHEME' in environ:
+ environ['wsgi.url_scheme'] = environ.pop('HTTP_X_FORWARDED_SCHEME')
+ elif 'HTTP_X_FORWARDED_PROTO' in environ:
+ environ['wsgi.url_scheme'] = environ.pop('HTTP_X_FORWARDED_PROTO')
+ if self.force_port is not None:
+ host = environ.get('HTTP_HOST', '').split(':', 1)[0]
+ if self.force_port:
+ host = '%s:%s' % (host, self.force_port)
+ environ['SERVER_PORT'] = str(self.force_port)
+ else:
+ if environ['wsgi.url_scheme'] == 'http':
+ port = '80'
+ else:
+ port = '443'
+ environ['SERVER_PORT'] = port
+ environ['HTTP_HOST'] = host
+ if self.scheme is not None:
+ environ['wsgi.url_scheme'] = self.scheme
+ return self.app(environ, start_response)
+
+
+def make_prefix_middleware(
+ app, global_conf, prefix='/',
+ translate_forwarded_server=True,
+ force_port=None, scheme=None):
+ from paste.deploy.converters import asbool
+ translate_forwarded_server = asbool(translate_forwarded_server)
+ return PrefixMiddleware(
+ app, prefix=prefix,
+ translate_forwarded_server=translate_forwarded_server,
+ force_port=force_port, scheme=scheme)
+
+make_prefix_middleware.__doc__ = PrefixMiddleware.__doc__