Commits

Olemis Lang committed 85fed3a

BH Multiproduct #441 : Trac web bootstrap handler (v1)

  • Participants
  • Parent commits d48ae86
  • Branches t441_product_dispatch

Comments (0)

Files changed (2)

+t441/t441_r1468583_trac_bootstrap_handler.diff
 #t441/t441_r1452788_product_dispatch.diff
 #t441/t441_r1461631_product_module_pathinfo.diff
 #t441/t441_r1462483_product_module_testing.diff

t441/t441_r1468583_trac_bootstrap_handler.diff

+# HG changeset patch
+# Parent 7a0027922003896654666d7d3351847093460ffb
+BH Multiproduct #441 : Web bootstrap handler
+
+diff -r 7a0027922003 trac/trac/hooks.py
+--- a/trac/trac/hooks.py	Tue Apr 16 20:03:40 2013 +0000
++++ b/trac/trac/hooks.py	Wed Apr 17 12:08:53 2013 -0500
+@@ -19,18 +19,23 @@
+ import imp
+ import inspect
+ 
+-from config import Configuration
+-from util.concurrency import threading
++from trac.config import Configuration
++from trac.env import open_environment
++from trac.util.concurrency import threading
++from trac.web.api import RequestDone
++from trac.web.href import Href
++from trac.web.main import RequestWithSession
++
+ 
+ __all__ = ['environment_factory', 'install_global_hooks']
+ 
+ class EnvironmentFactoryBase(object):
+     def open_environment(self, environ, env_path, global_env, use_cache=False):
+-        return None
++        raise NotImplementedError("Must override method 'open_environment'")
+ 
+ class RequestFactoryBase(object):
+     def create_request(self, env, environ, start_response):
+-        return None
++        raise NotImplementedError("Must override method 'create_request'")
+ 
+ def _get_plugins_dir(env_path):
+     return os.path.normcase(os.path.realpath(os.path.join(env_path, 'plugins')))
+@@ -81,3 +86,139 @@
+ def request_factory(env):
+     hook_path = env.config.get('trac', 'request_factory', default=None)
+     return _get_hook_class(env.path, hook_path, RequestFactoryBase) if hook_path else None
++
++class BootstrapHandlerBase(object):
++    """Objects responsible for loading the target environment and
++    request objects used in subsequent dispatching. 
++    """
++    def open_environment(self, environ, start_response):
++        """Load and initialize target Trac environment involved in request
++        dispatching.
++
++        The following WSGI entries will also be present in `environ` dict:
++
++        ||= WSGI variable =||= Environment variable =||= Comment =||
++        || trac.env_path || TRAC_ENV || See wiki:TracModWSGI ||
++        || trac.env_parent_dir || TRAC_ENV_PARENT_DIR || See wiki:TracModWSGI||
++        || trac.env_index_template || TRAC_ENV_INDEX_TEMPLATE || See wiki:TracInterfaceCustomization ||
++        || trac.template_vars || TRAC_TEMPLATE_VARS || See wiki:TracInterfaceCustomization ||
++        || trac.locale ||  || Target locale ||
++        || trac.base_url || TRAC_BASE_URL || Trac base URL hint ||
++
++        This method may handle the request (e.g. render environment index page)
++        in case environment lookup yields void results. In that case it MUST 
++        invoke WSGI `write` callable returned by `start_response` and raise 
++        `trac.web.api.RequestDone` exception.
++
++        :param environ: WSGI environment dict
++        :param start_response: WSGI callback for starting the response
++        :throws RequestDone: if the request is fully processed while loading
++                             target environment e.g. environment index page
++        :throws EnvironmentError: if it is impossible to find a way to locate
++                                  target environment e.g. TRAC_ENV and 
++                                  TRAC_ENV_PARENT_DIR both missing
++        :throws Exception: any other exception will be processed by the caller 
++                           in order to send a generic error message back to
++                           the HTTP client
++        """
++        raise NotImplementedError("Must override method 'open_environment'")
++
++    def create_request(self, env, environ, start_response):
++        """Instantiate request object used in subsequent request dispatching
++        
++        :param env: target Trac environment returned by `open_environment`
++        :param environ: WSGI environment dict
++        :param start_response: WSGI callback for starting the response
++        """
++        raise NotImplementedError("Must override method 'create_request'")
++
++
++class DefaultBootstrapHandler(BootstrapHandlerBase):
++    """Default bootstrap handler
++    
++    - Load environment based on URL path.
++    - Instantiate RequestWithSession
++    
++    Notice: This class is a straightforward refactoring of factories
++    implementation.
++    """
++    global_env = None
++
++    def open_environment(self, environ, start_response):
++        env_path = environ.get('trac.env_path')
++        if not env_path:
++            env_parent_dir = environ.get('trac.env_parent_dir')
++            env_paths = environ.get('trac.env_paths')
++            if env_parent_dir or env_paths:
++                # The first component of the path is the base name of the
++                # environment
++                path_info = environ.get('PATH_INFO', '').lstrip('/').split('/')
++                env_name = path_info.pop(0)
++    
++                if not env_name:
++                    # No specific environment requested, so render an environment
++                    # index page
++                    send_project_index(environ, start_response, env_parent_dir,
++                                       env_paths)
++                    raise RequestDone
++    
++                errmsg = None
++    
++                # To make the matching patterns of request handlers work, we append
++                # the environment name to the `SCRIPT_NAME` variable, and keep only
++                # the remaining path in the `PATH_INFO` variable.
++                script_name = environ.get('SCRIPT_NAME', '')
++                try:
++                    script_name = unicode(script_name, 'utf-8')
++                    # (as Href expects unicode parameters)
++                    environ['SCRIPT_NAME'] = Href(script_name)(env_name)
++                    environ['PATH_INFO'] = '/' + '/'.join(path_info)
++    
++                    if env_parent_dir:
++                        env_path = os.path.join(env_parent_dir, env_name)
++                    else:
++                        env_path = get_environments(environ).get(env_name)
++    
++                    if not env_path or not os.path.isdir(env_path):
++                        errmsg = 'Environment not found'
++                except UnicodeDecodeError:
++                    errmsg = 'Invalid URL encoding (was %r)' % script_name
++    
++                if errmsg:
++                    write = start_response('404 Not Found',
++                                   [('Content-Type', 'text/plain'),
++                                    ('Content-Length', str(len(errmsg)))])
++                    write(errmsg)
++                    raise RequestDone
++    
++        if not env_path:
++            raise EnvironmentError('The environment options "TRAC_ENV" or '
++                                   '"TRAC_ENV_PARENT_DIR" or the mod_python '
++                                   'options "TracEnv" or "TracEnvParentDir" are '
++                                   'missing. Trac requires one of these options '
++                                   'to locate the Trac environment(s).')
++        run_once = environ['wsgi.run_once']
++    
++        env = None
++        self.global_env = global_env = None
++        try:
++            self.global_env = global_env = open_environment(env_path, use_cache=not run_once)
++            factory = environment_factory(global_env)
++            factory_env = factory().open_environment(environ, env_path, global_env, use_cache=not run_once) if factory \
++                            else None
++            env = factory_env if factory_env else global_env
++        except Exception:
++            raise
++        return env
++
++    def create_request(self, env, environ, start_response):
++        factory = None
++        try:
++            factory = request_factory(self.global_env)
++        except AttributeError:
++            pass
++        return factory().create_request(env, environ, start_response) if factory \
++                else RequestWithSession(environ, start_response)
++
++# Recursive imports
++from trac.web.main import send_project_index, get_environments
+diff -r 7a0027922003 trac/trac/web/main.py
+--- a/trac/trac/web/main.py	Tue Apr 16 20:03:40 2013 +0000
++++ b/trac/trac/web/main.py	Wed Apr 17 12:08:53 2013 -0500
+@@ -379,100 +379,49 @@
+ 
+     locale.setlocale(locale.LC_ALL, environ['trac.locale'])
+ 
++    # FIXME: Load custom bootstrap handler
++    from trac.hooks import DefaultBootstrapHandler
++    bootstrap = DefaultBootstrapHandler()
++
+     # Determine the environment
+-    env_path = environ.get('trac.env_path')
+-    if not env_path:
+-        env_parent_dir = environ.get('trac.env_parent_dir')
+-        env_paths = environ.get('trac.env_paths')
+-        if env_parent_dir or env_paths:
+-            # The first component of the path is the base name of the
+-            # environment
+-            path_info = environ.get('PATH_INFO', '').lstrip('/').split('/')
+-            env_name = path_info.pop(0)
++    
++    env = env_error = None
++    try:
++        env = bootstrap.open_environment(environ, start_response)
++    except RequestDone:
++        return []
++    except EnvironmentError:
++        raise
++    except Exception, e:
++        env_error = e
++    else:
++        try:
++            if env.base_url_for_redirect:
++                environ['trac.base_url'] = env.base_url
++    
++            # Web front-end type and version information
++            if not hasattr(env, 'webfrontend'):
++                mod_wsgi_version = environ.get('mod_wsgi.version')
++                if mod_wsgi_version:
++                    mod_wsgi_version = (
++                            "%s (WSGIProcessGroup %s WSGIApplicationGroup %s)" %
++                            ('.'.join([str(x) for x in mod_wsgi_version]),
++                             environ.get('mod_wsgi.process_group'),
++                             environ.get('mod_wsgi.application_group') or
++                             '%{GLOBAL}'))
++                    environ.update({
++                        'trac.web.frontend': 'mod_wsgi',
++                        'trac.web.version': mod_wsgi_version})
++                env.webfrontend = environ.get('trac.web.frontend')
++                if env.webfrontend:
++                    env.systeminfo.append((env.webfrontend,
++                                           environ['trac.web.version']))
++        except Exception, e:
++            env_error = e
+ 
+-            if not env_name:
+-                # No specific environment requested, so render an environment
+-                # index page
+-                send_project_index(environ, start_response, env_parent_dir,
+-                                   env_paths)
+-                return []
+-
+-            errmsg = None
+-
+-            # To make the matching patterns of request handlers work, we append
+-            # the environment name to the `SCRIPT_NAME` variable, and keep only
+-            # the remaining path in the `PATH_INFO` variable.
+-            script_name = environ.get('SCRIPT_NAME', '')
+-            try:
+-                script_name = unicode(script_name, 'utf-8')
+-                # (as Href expects unicode parameters)
+-                environ['SCRIPT_NAME'] = Href(script_name)(env_name)
+-                environ['PATH_INFO'] = '/' + '/'.join(path_info)
+-
+-                if env_parent_dir:
+-                    env_path = os.path.join(env_parent_dir, env_name)
+-                else:
+-                    env_path = get_environments(environ).get(env_name)
+-
+-                if not env_path or not os.path.isdir(env_path):
+-                    errmsg = 'Environment not found'
+-            except UnicodeDecodeError:
+-                errmsg = 'Invalid URL encoding (was %r)' % script_name
+-
+-            if errmsg:
+-                start_response('404 Not Found',
+-                               [('Content-Type', 'text/plain'),
+-                                ('Content-Length', str(len(errmsg)))])
+-                return [errmsg]
+-
+-    if not env_path:
+-        raise EnvironmentError('The environment options "TRAC_ENV" or '
+-                               '"TRAC_ENV_PARENT_DIR" or the mod_python '
+-                               'options "TracEnv" or "TracEnvParentDir" are '
+-                               'missing. Trac requires one of these options '
+-                               'to locate the Trac environment(s).')
+     run_once = environ['wsgi.run_once']
+ 
+-    env = env_error = None
+-    global_env = None
+-    try:
+-        from trac.hooks import environment_factory
+-        global_env = open_environment(env_path, use_cache=not run_once)
+-        factory = environment_factory(global_env)
+-        factory_env = factory().open_environment(environ, env_path, global_env, use_cache=not run_once) if factory \
+-                        else None
+-        env = factory_env if factory_env else global_env
+-        if env.base_url_for_redirect:
+-            environ['trac.base_url'] = env.base_url
+-
+-        # Web front-end type and version information
+-        if not hasattr(env, 'webfrontend'):
+-            mod_wsgi_version = environ.get('mod_wsgi.version')
+-            if mod_wsgi_version:
+-                mod_wsgi_version = (
+-                        "%s (WSGIProcessGroup %s WSGIApplicationGroup %s)" %
+-                        ('.'.join([str(x) for x in mod_wsgi_version]),
+-                         environ.get('mod_wsgi.process_group'),
+-                         environ.get('mod_wsgi.application_group') or
+-                         '%{GLOBAL}'))
+-                environ.update({
+-                    'trac.web.frontend': 'mod_wsgi',
+-                    'trac.web.version': mod_wsgi_version})
+-            env.webfrontend = environ.get('trac.web.frontend')
+-            if env.webfrontend:
+-                env.systeminfo.append((env.webfrontend,
+-                                       environ['trac.web.version']))
+-    except Exception, e:
+-        env_error = e
+-
+-    from trac.hooks import request_factory
+-    factory = None
+-    try:
+-        factory = request_factory(global_env)
+-    except AttributeError:
+-        pass
+-    req = factory().create_request(env, environ, start_response) if factory \
+-            else RequestWithSession(environ, start_response)
++    req = bootstrap.create_request(env, environ, start_response)
+     translation.make_activable(lambda: req.locale, env.path if env else None)
+     try:
+         return _dispatch_request(req, env, env_error)