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

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

Import inicial.

File size: 14.3 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"""
4Middleware to make internal requests and forward requests internally.
5
6When applied, several keys are added to the environment that will allow
7you to trigger recursive redirects and forwards.
8
9  paste.recursive.include:
10      When you call
11      ``environ['paste.recursive.include'](new_path_info)`` a response
12      will be returned.  The response has a ``body`` attribute, a
13      ``status`` attribute, and a ``headers`` attribute.
14
15  paste.recursive.script_name:
16      The ``SCRIPT_NAME`` at the point that recursive lives.  Only
17      paths underneath this path can be redirected to.
18
19  paste.recursive.old_path_info:
20      A list of previous ``PATH_INFO`` values from previous redirects.
21
22Raise ``ForwardRequestException(new_path_info)`` to do a forward
23(aborting the current request).
24"""
25
26from cStringIO import StringIO
27import warnings
28
29__all__ = ['RecursiveMiddleware']
30__pudge_all__ =  ['RecursiveMiddleware', 'ForwardRequestException']
31
32class RecursionLoop(AssertionError):
33    # Subclasses AssertionError for legacy reasons
34    """Raised when a recursion enters into a loop"""
35
36class CheckForRecursionMiddleware(object):
37    def __init__(self, app, env):
38        self.app = app
39        self.env = env
40
41    def __call__(self, environ, start_response):
42        path_info = environ.get('PATH_INFO','')
43        if path_info in self.env.get(
44            'paste.recursive.old_path_info', []):
45            raise RecursionLoop(
46                "Forwarding loop detected; %r visited twice (internal "
47                "redirect path: %s)"
48                % (path_info, self.env['paste.recursive.old_path_info']))
49        old_path_info = self.env.setdefault('paste.recursive.old_path_info', [])
50        old_path_info.append(self.env.get('PATH_INFO', ''))
51        return self.app(environ, start_response)
52
53class RecursiveMiddleware(object):
54
55    """
56    A WSGI middleware that allows for recursive and forwarded calls.
57    All these calls go to the same 'application', but presumably that
58    application acts differently with different URLs.  The forwarded
59    URLs must be relative to this container.
60
61    Interface is entirely through the ``paste.recursive.forward`` and
62    ``paste.recursive.include`` environmental keys.
63    """
64
65    def __init__(self, application, global_conf=None):
66        self.application = application
67
68    def __call__(self, environ, start_response):
69        environ['paste.recursive.forward'] = Forwarder(
70            self.application,
71            environ,
72            start_response)
73        environ['paste.recursive.include'] = Includer(
74            self.application,
75            environ,
76            start_response)
77        environ['paste.recursive.include_app_iter'] = IncluderAppIter(
78            self.application,
79            environ,
80            start_response)
81        my_script_name = environ.get('SCRIPT_NAME', '')
82        environ['paste.recursive.script_name'] = my_script_name
83        try:
84            return self.application(environ, start_response)
85        except ForwardRequestException, e:
86            middleware = CheckForRecursionMiddleware(
87                e.factory(self), environ)
88            return middleware(environ, start_response)
89
90class ForwardRequestException(Exception):
91    """
92    Used to signal that a request should be forwarded to a different location.
93
94    ``url``
95        The URL to forward to starting with a ``/`` and relative to
96        ``RecursiveMiddleware``. URL fragments can also contain query strings
97        so ``/error?code=404`` would be a valid URL fragment.
98
99    ``environ``
100        An altertative WSGI environment dictionary to use for the forwarded
101        request. If specified is used *instead* of the ``url_fragment``
102
103    ``factory``
104        If specifed ``factory`` is used instead of ``url`` or ``environ``.
105        ``factory`` is a callable that takes a WSGI application object
106        as the first argument and returns an initialised WSGI middleware
107        which can alter the forwarded response.
108
109    Basic usage (must have ``RecursiveMiddleware`` present) :
110
111    .. code-block:: python
112
113        from paste.recursive import ForwardRequestException
114        def app(environ, start_response):
115            if environ['PATH_INFO'] == '/hello':
116                start_response("200 OK", [('Content-type', 'text/plain')])
117                return ['Hello World!']
118            elif environ['PATH_INFO'] == '/error':
119                start_response("404 Not Found", [('Content-type', 'text/plain')])
120                return ['Page not found']
121            else:
122                raise ForwardRequestException('/error')
123
124        from paste.recursive import RecursiveMiddleware
125        app = RecursiveMiddleware(app)
126
127    If you ran this application and visited ``/hello`` you would get a
128    ``Hello World!`` message. If you ran the application and visited
129    ``/not_found`` a ``ForwardRequestException`` would be raised and the caught
130    by the ``RecursiveMiddleware``. The ``RecursiveMiddleware`` would then
131    return the headers and response from the ``/error`` URL but would display
132    a ``404 Not found`` status message.
133
134    You could also specify an ``environ`` dictionary instead of a url. Using
135    the same example as before:
136
137    .. code-block:: python
138
139        def app(environ, start_response):
140            ... same as previous example ...
141            else:
142                new_environ = environ.copy()
143                new_environ['PATH_INFO'] = '/error'
144                raise ForwardRequestException(environ=new_environ)
145
146    Finally, if you want complete control over every aspect of the forward you
147    can specify a middleware factory. For example to keep the old status code
148    but use the headers and resposne body from the forwarded response you might
149    do this:
150
151    .. code-block:: python
152
153        from paste.recursive import ForwardRequestException
154        from paste.recursive import RecursiveMiddleware
155        from paste.errordocument import StatusKeeper
156
157        def app(environ, start_response):
158            if environ['PATH_INFO'] == '/hello':
159                start_response("200 OK", [('Content-type', 'text/plain')])
160                return ['Hello World!']
161            elif environ['PATH_INFO'] == '/error':
162                start_response("404 Not Found", [('Content-type', 'text/plain')])
163                return ['Page not found']
164            else:
165                def factory(app):
166                    return StatusKeeper(app, status='404 Not Found', url='/error')
167                raise ForwardRequestException(factory=factory)
168
169        app = RecursiveMiddleware(app)
170    """
171
172    def __init__(
173        self,
174        url=None,
175        environ={},
176        factory=None,
177        path_info=None):
178        # Check no incompatible options have been chosen
179        if factory and url:
180            raise TypeError(
181                'You cannot specify factory and a url in '
182                'ForwardRequestException')
183        elif factory and environ:
184            raise TypeError(
185                'You cannot specify factory and environ in '
186                'ForwardRequestException')
187        if url and environ:
188            raise TypeError(
189                'You cannot specify environ and url in '
190                'ForwardRequestException')
191
192        # set the path_info or warn about its use.
193        if path_info:
194            if not url:
195                warnings.warn(
196                    "ForwardRequestException(path_info=...) has been deprecated; please "
197                    "use ForwardRequestException(url=...)",
198                    DeprecationWarning, 2)
199            else:
200                raise TypeError('You cannot use url and path_info in ForwardRequestException')
201            self.path_info = path_info
202
203        # If the url can be treated as a path_info do that
204        if url and not '?' in str(url):
205            self.path_info = url
206
207        # Base middleware
208        class ForwardRequestExceptionMiddleware(object):
209            def __init__(self, app):
210                self.app = app
211
212        # Otherwise construct the appropriate middleware factory
213        if hasattr(self, 'path_info'):
214            p = self.path_info
215            def factory_(app):
216                class PathInfoForward(ForwardRequestExceptionMiddleware):
217                    def __call__(self, environ, start_response):
218                        environ['PATH_INFO'] = p
219                        return self.app(environ, start_response)
220                return PathInfoForward(app)
221            self.factory = factory_
222        elif url:
223            def factory_(app):
224                class URLForward(ForwardRequestExceptionMiddleware):
225                    def __call__(self, environ, start_response):
226                        environ['PATH_INFO'] = url.split('?')[0]
227                        environ['QUERY_STRING'] = url.split('?')[1]
228                        return self.app(environ, start_response)
229                return URLForward(app)
230            self.factory = factory_
231        elif environ:
232            def factory_(app):
233                class EnvironForward(ForwardRequestExceptionMiddleware):
234                    def __call__(self, environ_, start_response):
235                        return self.app(environ, start_response)
236                return EnvironForward(app)
237            self.factory = factory_
238        else:
239            self.factory = factory
240
241class Recursive(object):
242
243    def __init__(self, application, environ, start_response):
244        self.application = application
245        self.original_environ = environ.copy()
246        self.previous_environ = environ
247        self.start_response = start_response
248
249    def __call__(self, path, extra_environ=None):
250        """
251        `extra_environ` is an optional dictionary that is also added
252        to the forwarded request.  E.g., ``{'HTTP_HOST': 'new.host'}``
253        could be used to forward to a different virtual host.
254        """
255        environ = self.original_environ.copy()
256        if extra_environ:
257            environ.update(extra_environ)
258        environ['paste.recursive.previous_environ'] = self.previous_environ
259        base_path = self.original_environ.get('SCRIPT_NAME')
260        if path.startswith('/'):
261            assert path.startswith(base_path), (
262                "You can only forward requests to resources under the "
263                "path %r (not %r)" % (base_path, path))
264            path = path[len(base_path)+1:]
265        assert not path.startswith('/')
266        path_info = '/' + path
267        environ['PATH_INFO'] = path_info
268        environ['REQUEST_METHOD'] = 'GET'
269        environ['CONTENT_LENGTH'] = '0'
270        environ['CONTENT_TYPE'] = ''
271        environ['wsgi.input'] = StringIO('')
272        return self.activate(environ)
273
274    def activate(self, environ):
275        raise NotImplementedError
276
277    def __repr__(self):
278        return '<%s.%s from %s>' % (
279            self.__class__.__module__,
280            self.__class__.__name__,
281            self.original_environ.get('SCRIPT_NAME') or '/')
282
283class Forwarder(Recursive):
284
285    """
286    The forwarder will try to restart the request, except with
287    the new `path` (replacing ``PATH_INFO`` in the request).
288
289    It must not be called after and headers have been returned.
290    It returns an iterator that must be returned back up the call
291    stack, so it must be used like:
292
293    .. code-block:: python
294
295        return environ['paste.recursive.forward'](path)
296
297    Meaningful transformations cannot be done, since headers are
298    sent directly to the server and cannot be inspected or
299    rewritten.
300    """
301
302    def activate(self, environ):
303        warnings.warn(
304            "recursive.Forwarder has been deprecated; please use "
305            "ForwardRequestException",
306            DeprecationWarning, 2)
307        return self.application(environ, self.start_response)
308
309
310class Includer(Recursive):
311
312    """
313    Starts another request with the given path and adding or
314    overwriting any values in the `extra_environ` dictionary.
315    Returns an IncludeResponse object.
316    """
317
318    def activate(self, environ):
319        response = IncludedResponse()
320        def start_response(status, headers, exc_info=None):
321            if exc_info:
322                raise exc_info[0], exc_info[1], exc_info[2]
323            response.status = status
324            response.headers = headers
325            return response.write
326        app_iter = self.application(environ, start_response)
327        try:
328            for s in app_iter:
329                response.write(s)
330        finally:
331            if hasattr(app_iter, 'close'):
332                app_iter.close()
333        response.close()
334        return response
335
336class IncludedResponse(object):
337
338    def __init__(self):
339        self.headers = None
340        self.status = None
341        self.output = StringIO()
342        self.str = None
343
344    def close(self):
345        self.str = self.output.getvalue()
346        self.output.close()
347        self.output = None
348
349    def write(self, s):
350        assert self.output is not None, (
351            "This response has already been closed and no further data "
352            "can be written.")
353        self.output.write(s)
354
355    def __str__(self):
356        return self.body
357
358    def body__get(self):
359        if self.str is None:
360            return self.output.getvalue()
361        else:
362            return self.str
363    body = property(body__get)
364
365
366class IncluderAppIter(Recursive):
367    """
368    Like Includer, but just stores the app_iter response
369    (be sure to call close on the response!)
370    """
371
372    def activate(self, environ):
373        response = IncludedAppIterResponse()
374        def start_response(status, headers, exc_info=None):
375            if exc_info:
376                raise exc_info[0], exc_info[1], exc_info[2]
377            response.status = status
378            response.headers = headers
379            return response.write
380        app_iter = self.application(environ, start_response)
381        response.app_iter = app_iter
382        return response
383
384class IncludedAppIterResponse(object):
385
386    def __init__(self):
387        self.status = None
388        self.headers = None
389        self.accumulated = []
390        self.app_iter = None
391        self._closed = False
392
393    def close(self):
394        assert not self._closed, (
395            "Tried to close twice")
396        if hasattr(self.app_iter, 'close'):
397            self.app_iter.close()
398
399    def write(self, s):
400        self.accumulated.append
401
402def make_recursive_middleware(app, global_conf):
403    return RecursiveMiddleware(app)
404
405make_recursive_middleware.__doc__ = __doc__
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.