Commits

Ginés Martínez Sánchez  committed e302c0c Draft

developing wsgi server

  • Participants
  • Parent commits 1ee4e85

Comments (0)

Files changed (4)

File ginsfsm/gaplic.py

 }
 
 GAPLIC_GCONFIG = {
-    'global_settings': [dict, {}, 0, None,
-        'The global settings will be set to all new created gobj'
+    'ini_settings': [dict, {}, 0, None,
+        'The ini settings will be set to all new created gobj'
         ' by overwrite_parameters() function'],
     # trace_mach is inherited from SMachine.
     'trace_mach': [bool, False, 0, None, 'Display simple machine activity'],
     """ Container of gobj's running under the same process or thread.
 
     :param name: name of the gaplic, default is ``None``.
-    :param global_settings: keyword arguments,
-        with the global parameters from a ini configfile.
-        The global settings will be set to all new created gobj
+    :param ini_settings: keyword arguments,
+        with the parameters from a ini configfile.
+        The ini settings will be set to all new created gobj
         by :func:`ginsfsm.gobj.GObj.overwrite_parameters` function.
 
 
                 print('Program stopped')
 
     """
-    def __init__(self, name=None, **global_settings):
+    def __init__(self, name=None, **ini_settings):
         GObj.__init__(self, GAPLIC_FSM)
         self.name = name
-        self.global_settings = global_settings.copy()
+        self.ini_settings = ini_settings.copy()
         # Call shutdown() to stop gaplic
         self.do_exit = multiprocessing.Event()
         """threading.Event() or multiprocessing.Event() object
         self.gaplic = self
         self.deferred_list = DeferredList()
 
-        logger = global_settings.get('logger', None)
+        logger = ini_settings.get('logger', None)
         if logger is None:
             # TODO use package caller
             self.logger = logging.getLogger(__name__)
         _gaplic_kw = {
             'gaplic': self,
             'logger': self.logger,
-            'global_settings': self.global_settings,
+            'ini_settings': self.ini_settings,
             'create_gobj': self.create_gobj,
             'enqueue_event': self.enqueue_event,
             'register_unique_gobj': self.register_unique_gobj,
         host = 0.0.0.0
         port = 8001
         wsgi-server.application = app:wsgi-application
+        # TODO: wsgi-server.application = composite:urlmap
 
         [app:wsgi-application]
-        use = call:ginsfsm.examples.example6-wsgi-server:paste_app_factory
+        use = call:ginsfsm.examples.wsgi.simple_wsgi_server:paste_app_factory
+
+        [composite:urlmap]
+        use = call:ginsfsm.protocols.wsgi.common.urlmap:urlmap_factory
+        / = home
+        /blog = blog
+        /wiki = wiki
+
+        [app:home]
+        use = call:ginsfsm.examples.wsgi.simple_wsgi_server:paste_app_factory
+
+        [app:blog]
+        use = call:ginsfsm.examples.wsgi.simple_wsgi_server:paste_app_factory
+
+        [app:wiki]
+        use = call:ginsfsm.examples.wsgi.simple_wsgi_server:paste_app_factory
 
     The prototype for ``others`` (paste app factory) is::
 

File ginsfsm/gobj.py

 # Attributes that a gaplic can update.
 GOBJ_GCONFIG = {
     'gaplic': [None, None, 0, None, ''],
-    'global_settings': [dict, {}, 0, None,
-        'The global settings will be set to all new created gobj'
+    'ini_settings': [dict, {}, 0, None,
+        'The ini settings will be set to all new created gobj'
         ' by overwrite_parameters() function'],
     # trace_mach is inherited from SMachine.
     'trace_mach': [bool, False, 0, None, 'Display simple machine activity'],
                     '''ERROR create_gobj():'''
                     ''' cannot register_unique_gobj '%s' ''' % (name))
 
-        # What must be the first? arguments or global settings??
-        if self.global_settings is not None:
-            # global not win.
-            gobj.overwrite_parameters(0, **self.global_settings)
-        # particular wins.
+        # Who wins? arguments or file ini settings?
         gobj.write_parameters(**kw)
+        if self.ini_settings is not None:
+            # ini global win.
+            gobj.overwrite_parameters(0, **self.ini_settings)
 
         if parent is not None:
             parent._add_child(gobj)

File ginsfsm/protocols/http/common/utilities.py

     reason = 'Bad Request'
 
 
+class NotFound(Error):
+    code = 404
+    reason = 'Not Found.'
+
+
 class RequestHeaderFieldsTooLarge(BadRequest):
     code = 431
     reason = 'Request Header Fields Too Large'

File ginsfsm/protocols/wsgi/common/urlmap.py

+# (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
+"""
+Map URL prefixes to WSGI applications.  See ``URLMap``
+"""
+
+from UserDict import DictMixin
+import re
+import os
+import cgi
+
+__all__ = ['URLMap', 'PathProxyURLMap']
+
+
+def urlmap_factory(loader, global_conf, **local_conf):
+    if 'not_found_app' in local_conf:
+        not_found_app = local_conf.pop('not_found_app')
+    else:
+        not_found_app = global_conf.get('not_found_app')
+    if not_found_app:
+        not_found_app = loader.get_app(not_found_app, global_conf=global_conf)
+    urlmap = URLMap(not_found_app=not_found_app)
+    for path, app_name in local_conf.items():
+        path = parse_path_expression(path)
+        app = loader.get_app(app_name, global_conf=global_conf)
+        urlmap[path] = app
+    return urlmap
+
+
+def parse_path_expression(path):
+    """
+    Parses a path expression like 'domain foobar.com port 20 /' or
+    just '/foobar' for a path alone.  Returns as an address that
+    URLMap likes.
+    """
+    parts = path.split()
+    domain = port = path = None
+    while parts:
+        if parts[0] == 'domain':
+            parts.pop(0)
+            if not parts:
+                raise ValueError(
+                    "'domain' must be followed with a domain name")
+            if domain:
+                raise ValueError("'domain' given twice")
+            domain = parts.pop(0)
+        elif parts[0] == 'port':
+            parts.pop(0)
+            if not parts:
+                raise ValueError("'port' must be followed with a port number")
+            if port:
+                raise ValueError("'port' given twice")
+            port = parts.pop(0)
+        else:
+            if path:
+                raise ValueError("more than one path given (have %r, got %r)"
+                                 % (path, parts[0]))
+            path = parts.pop(0)
+    s = ''
+    if domain:
+        s = 'http://%s' % domain
+    if port:
+        if not domain:
+            raise ValueError("If you give a port, you must also give a domain")
+        s += ':' + port
+    if path:
+        if s:
+            s += '/'
+        s += path
+    return s
+
+
+class URLMap(DictMixin):
+
+    """
+    URLMap instances are dictionary-like object that dispatch to one
+    of several applications based on the URL.
+
+    The dictionary keys are URLs to match (like
+    ``PATH_INFO.startswith(url)``), and the values are applications to
+    dispatch to.  URLs are matched most-specific-first, i.e., longest
+    URL first.  The ``SCRIPT_NAME`` and ``PATH_INFO`` environmental
+    variables are adjusted to indicate the new context.
+
+    URLs can also include domains, like ``http://blah.com/foo``, or as
+    tuples ``('blah.com', '/foo')``.  This will match domain names; without
+    the ``http://domain`` or with a domain of ``None`` any domain will be
+    matched (so long as no other explicit domain matches).  """
+
+    def __init__(self, not_found_app=None):
+        self.applications = []
+        if not not_found_app:
+            not_found_app = self.not_found_app
+        self.not_found_application = not_found_app
+
+    norm_url_re = re.compile('//+')
+    domain_url_re = re.compile('^(http|https)://')
+
+    def not_found_app(self, environ, start_response):
+        mapper = environ.get('paste.urlmap_object')
+        if mapper:
+            matches = [p for p, a in mapper.applications]
+            extra = 'defined apps: %s' % (
+                ',\n  '.join(map(repr, matches)))
+        else:
+            extra = ''
+        extra += '\nSCRIPT_NAME: %r' % environ.get('SCRIPT_NAME')
+        extra += '\nPATH_INFO: %r' % environ.get('PATH_INFO')
+        extra += '\nHTTP_HOST: %r' % environ.get('HTTP_HOST')
+        # NotFound('The resource could not be found.')
+        from paste import httpexceptions
+        app = httpexceptions.HTTPNotFound(
+            environ['PATH_INFO'],
+            comment=cgi.escape(extra)).wsgi_application
+        return app(environ, start_response)
+
+    def normalize_url(self, url, trim=True):
+        if isinstance(url, (list, tuple)):
+            domain = url[0]
+            url = self.normalize_url(url[1])[1]
+            return domain, url
+        assert (not url or url.startswith('/')
+                or self.domain_url_re.search(url)), (
+            "URL fragments must start with / or http:// (you gave %r)" % url)
+        match = self.domain_url_re.search(url)
+        if match:
+            url = url[match.end():]
+            if '/' in url:
+                domain, url = url.split('/', 1)
+                url = '/' + url
+            else:
+                domain, url = url, ''
+        else:
+            domain = None
+        url = self.norm_url_re.sub('/', url)
+        if trim:
+            url = url.rstrip('/')
+        return domain, url
+
+    def sort_apps(self):
+        """
+        Make sure applications are sorted with longest URLs first
+        """
+        def key(app_desc):
+            (domain, url), app = app_desc
+            if not domain:
+                # Make sure empty domains sort last:
+                return '\xff', -len(url)
+            else:
+                return domain, -len(url)
+        apps = [(key(desc), desc) for desc in self.applications]
+        apps.sort()
+        self.applications = [desc for (sortable, desc) in apps]
+
+    def __setitem__(self, url, app):
+        if app is None:
+            try:
+                del self[url]
+            except KeyError:
+                pass
+            return
+        dom_url = self.normalize_url(url)
+        if dom_url in self:
+            del self[dom_url]
+        self.applications.append((dom_url, app))
+        self.sort_apps()
+
+    def __getitem__(self, url):
+        dom_url = self.normalize_url(url)
+        for app_url, app in self.applications:
+            if app_url == dom_url:
+                return app
+        raise KeyError(
+            "No application with the url %r (domain: %r; existing: %s)"
+            % (url[1], url[0] or '*', self.applications))
+
+    def __delitem__(self, url):
+        url = self.normalize_url(url)
+        for app_url, app in self.applications:
+            if app_url == url:
+                self.applications.remove((app_url, app))
+                break
+        else:
+            raise KeyError(
+                "No application with the url %r" % (url,))
+
+    def keys(self):
+        return [app_url for app_url, app in self.applications]
+
+    def __call__(self, environ, start_response):
+        host = environ.get('HTTP_HOST', environ.get('SERVER_NAME')).lower()
+        if ':' in host:
+            host, port = host.split(':', 1)
+        else:
+            if environ['wsgi.url_scheme'] == 'http':
+                port = '80'
+            else:
+                port = '443'
+        path_info = environ.get('PATH_INFO')
+        path_info = self.normalize_url(path_info, False)[1]
+        for (domain, app_url), app in self.applications:
+            if domain and domain != host and domain != host + ':' + port:
+                continue
+            if (path_info == app_url
+                or path_info.startswith(app_url + '/')):
+                environ['SCRIPT_NAME'] += app_url
+                environ['PATH_INFO'] = path_info[len(app_url):]
+                return app(environ, start_response)
+        environ['paste.urlmap_object'] = self
+        return self.not_found_application(environ, start_response)
+
+
+class PathProxyURLMap(object):
+
+    """
+    This is a wrapper for URLMap that catches any strings that
+    are passed in as applications; these strings are treated as
+    filenames (relative to `base_path`) and are passed to the
+    callable `builder`, which will return an application.
+
+    This is intended for cases when configuration files can be
+    treated as applications.
+
+    `base_paste_url` is the URL under which all applications added through
+    this wrapper must go.  Use ``""`` if you want this to not
+    change incoming URLs.
+    """
+
+    def __init__(self, map, base_paste_url, base_path, builder):
+        self.map = map
+        self.base_paste_url = self.map.normalize_url(base_paste_url)
+        self.base_path = base_path
+        self.builder = builder
+
+    def __setitem__(self, url, app):
+        if isinstance(app, (str, unicode)):
+            app_fn = os.path.join(self.base_path, app)
+            app = self.builder(app_fn)
+        url = self.map.normalize_url(url)
+        # @@: This means http://foo.com/bar will potentially
+        # match foo.com, but /base_paste_url/bar, which is unintuitive
+        url = (url[0] or self.base_paste_url[0],
+               self.base_paste_url[1] + url[1])
+        self.map[url] = app
+
+    def __getattr__(self, attr):
+        return getattr(self.map, attr)
+
+    # This is really the only settable attribute
+    def not_found_application__get(self):
+        return self.map.not_found_application
+
+    def not_found_application__set(self, value):
+        self.map.not_found_application = value
+
+    not_found_application = property(not_found_application__get,
+                                     not_found_application__set)