source: publico/il.spdo/trunk/Paste-1.7.5.1-py2.6.egg/paste/urlmap.py @ 5327

Última Alteração nesse arquivo desde 5327 foi 5327, incluída por fabianosantos, 8 anos atrás

Import inicial.

File size: 8.8 KB
Linha 
1# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
2# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
3"""
4Map URL prefixes to WSGI applications.  See ``URLMap``
5"""
6
7from UserDict import DictMixin
8import re
9import os
10import cgi
11from paste import httpexceptions
12
13__all__ = ['URLMap', 'PathProxyURLMap']
14
15def urlmap_factory(loader, global_conf, **local_conf):
16    if 'not_found_app' in local_conf:
17        not_found_app = local_conf.pop('not_found_app')
18    else:
19        not_found_app = global_conf.get('not_found_app')
20    if not_found_app:
21        not_found_app = loader.get_app(not_found_app, global_conf=global_conf)
22    urlmap = URLMap(not_found_app=not_found_app)
23    for path, app_name in local_conf.items():
24        path = parse_path_expression(path)
25        app = loader.get_app(app_name, global_conf=global_conf)
26        urlmap[path] = app
27    return urlmap
28
29def parse_path_expression(path):
30    """
31    Parses a path expression like 'domain foobar.com port 20 /' or
32    just '/foobar' for a path alone.  Returns as an address that
33    URLMap likes.
34    """
35    parts = path.split()
36    domain = port = path = None
37    while parts:
38        if parts[0] == 'domain':
39            parts.pop(0)
40            if not parts:
41                raise ValueError("'domain' must be followed with a domain name")
42            if domain:
43                raise ValueError("'domain' given twice")
44            domain = parts.pop(0)
45        elif parts[0] == 'port':
46            parts.pop(0)
47            if not parts:
48                raise ValueError("'port' must be followed with a port number")
49            if port:
50                raise ValueError("'port' given twice")
51            port = parts.pop(0)
52        else:
53            if path:
54                raise ValueError("more than one path given (have %r, got %r)"
55                                 % (path, parts[0]))
56            path = parts.pop(0)
57    s = ''
58    if domain:
59        s = 'http://%s' % domain
60    if port:
61        if not domain:
62            raise ValueError("If you give a port, you must also give a domain")
63        s += ':' + port
64    if path:
65        if s:
66            s += '/'
67        s += path
68    return s
69
70class URLMap(DictMixin):
71
72    """
73    URLMap instances are dictionary-like object that dispatch to one
74    of several applications based on the URL.
75
76    The dictionary keys are URLs to match (like
77    ``PATH_INFO.startswith(url)``), and the values are applications to
78    dispatch to.  URLs are matched most-specific-first, i.e., longest
79    URL first.  The ``SCRIPT_NAME`` and ``PATH_INFO`` environmental
80    variables are adjusted to indicate the new context.
81
82    URLs can also include domains, like ``http://blah.com/foo``, or as
83    tuples ``('blah.com', '/foo')``.  This will match domain names; without
84    the ``http://domain`` or with a domain of ``None`` any domain will be
85    matched (so long as no other explicit domain matches).  """
86
87    def __init__(self, not_found_app=None):
88        self.applications = []
89        if not not_found_app:
90            not_found_app = self.not_found_app
91        self.not_found_application = not_found_app
92
93    norm_url_re = re.compile('//+')
94    domain_url_re = re.compile('^(http|https)://')
95
96    def not_found_app(self, environ, start_response):
97        mapper = environ.get('paste.urlmap_object')
98        if mapper:
99            matches = [p for p, a in mapper.applications]
100            extra = 'defined apps: %s' % (
101                ',\n  '.join(map(repr, matches)))
102        else:
103            extra = ''
104        extra += '\nSCRIPT_NAME: %r' % environ.get('SCRIPT_NAME')
105        extra += '\nPATH_INFO: %r' % environ.get('PATH_INFO')
106        extra += '\nHTTP_HOST: %r' % environ.get('HTTP_HOST')
107        app = httpexceptions.HTTPNotFound(
108            environ['PATH_INFO'],
109            comment=cgi.escape(extra)).wsgi_application
110        return app(environ, start_response)
111
112    def normalize_url(self, url, trim=True):
113        if isinstance(url, (list, tuple)):
114            domain = url[0]
115            url = self.normalize_url(url[1])[1]
116            return domain, url
117        assert (not url or url.startswith('/')
118                or self.domain_url_re.search(url)), (
119            "URL fragments must start with / or http:// (you gave %r)" % url)
120        match = self.domain_url_re.search(url)
121        if match:
122            url = url[match.end():]
123            if '/' in url:
124                domain, url = url.split('/', 1)
125                url = '/' + url
126            else:
127                domain, url = url, ''
128        else:
129            domain = None
130        url = self.norm_url_re.sub('/', url)
131        if trim:
132            url = url.rstrip('/')
133        return domain, url
134
135    def sort_apps(self):
136        """
137        Make sure applications are sorted with longest URLs first
138        """
139        def key(app_desc):
140            (domain, url), app = app_desc
141            if not domain:
142                # Make sure empty domains sort last:
143                return '\xff', -len(url)
144            else:
145                return domain, -len(url)
146        apps = [(key(desc), desc) for desc in self.applications]
147        apps.sort()
148        self.applications = [desc for (sortable, desc) in apps]
149
150    def __setitem__(self, url, app):
151        if app is None:
152            try:
153                del self[url]
154            except KeyError:
155                pass
156            return
157        dom_url = self.normalize_url(url)
158        if dom_url in self:
159            del self[dom_url]
160        self.applications.append((dom_url, app))
161        self.sort_apps()
162
163    def __getitem__(self, url):
164        dom_url = self.normalize_url(url)
165        for app_url, app in self.applications:
166            if app_url == dom_url:
167                return app
168        raise KeyError(
169            "No application with the url %r (domain: %r; existing: %s)"
170            % (url[1], url[0] or '*', self.applications))
171
172    def __delitem__(self, url):
173        url = self.normalize_url(url)
174        for app_url, app in self.applications:
175            if app_url == url:
176                self.applications.remove((app_url, app))
177                break
178        else:
179            raise KeyError(
180                "No application with the url %r" % (url,))
181
182    def keys(self):
183        return [app_url for app_url, app in self.applications]
184
185    def __call__(self, environ, start_response):
186        host = environ.get('HTTP_HOST', environ.get('SERVER_NAME')).lower()
187        if ':' in host:
188            host, port = host.split(':', 1)
189        else:
190            if environ['wsgi.url_scheme'] == 'http':
191                port = '80'
192            else:
193                port = '443'
194        path_info = environ.get('PATH_INFO')
195        path_info = self.normalize_url(path_info, False)[1]
196        for (domain, app_url), app in self.applications:
197            if domain and domain != host and domain != host+':'+port:
198                continue
199            if (path_info == app_url
200                or path_info.startswith(app_url + '/')):
201                environ['SCRIPT_NAME'] += app_url
202                environ['PATH_INFO'] = path_info[len(app_url):]
203                return app(environ, start_response)
204        environ['paste.urlmap_object'] = self
205        return self.not_found_application(environ, start_response)
206
207
208class PathProxyURLMap(object):
209
210    """
211    This is a wrapper for URLMap that catches any strings that
212    are passed in as applications; these strings are treated as
213    filenames (relative to `base_path`) and are passed to the
214    callable `builder`, which will return an application.
215
216    This is intended for cases when configuration files can be
217    treated as applications.
218
219    `base_paste_url` is the URL under which all applications added through
220    this wrapper must go.  Use ``""`` if you want this to not
221    change incoming URLs.
222    """
223
224    def __init__(self, map, base_paste_url, base_path, builder):
225        self.map = map
226        self.base_paste_url = self.map.normalize_url(base_paste_url)
227        self.base_path = base_path
228        self.builder = builder
229
230    def __setitem__(self, url, app):
231        if isinstance(app, (str, unicode)):
232            app_fn = os.path.join(self.base_path, app)
233            app = self.builder(app_fn)
234        url = self.map.normalize_url(url)
235        # @@: This means http://foo.com/bar will potentially
236        # match foo.com, but /base_paste_url/bar, which is unintuitive
237        url = (url[0] or self.base_paste_url[0],
238               self.base_paste_url[1] + url[1])
239        self.map[url] = app
240
241    def __getattr__(self, attr):
242        return getattr(self.map, attr)
243
244    # This is really the only settable attribute
245    def not_found_application__get(self):
246        return self.map.not_found_application
247    def not_found_application__set(self, value):
248        self.map.not_found_application = value
249    not_found_application = property(not_found_application__get,
250                                     not_found_application__set)
Note: Veja TracBrowser para ajuda no uso do navegador do trac.
 

The contents and data of this website are published under license:
Creative Commons 4.0 Brasil - Atribuir Fonte - Compartilhar Igual.