summaryrefslogtreecommitdiff
path: root/paste/recursive.py
blob: bfe82065bc24f2530d6236c0c5ba3a8b3f1d63e1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
"""
A WSGI middleware that allows for recursive and forwarded calls.
All these calls go to the same 'application', but presumably that
application acts differently with different URLs.  The forwarded
URLs must be relative to this container.

The forwarder is available through
``environ['paste.recursive.forward'](path, extra_environ=None)``,
the second argument is a dictionary of values to be added to the
request, overwriting any keys.  The forward will call start_response;
thus you must *not* call it after you have sent any output to the
server.  Also, it will return an iterator that must be returned up the
stack.  You may need to use exceptions to guarantee that this iterator
will be passed back through the application.

The includer is available through
``environ['paste.recursive.include'](path, extra_environ=None)``.
It is like forwarder, except it completes the request and returns a
response object.  The response object has three public attributes:
status, headers, and body.  The status is a string, headers is a list
of (header_name, header_value) tuples, and the body is a string.
"""

from cStringIO import StringIO

class RecursiveMiddleware(object):

    def __init__(self, application):
        self.application = application

    def __call__(self, environ, start_response):
        environ['paste.recursive.forward'] = Forwarder(
            self.application, environ, start_response)
        environ['paste.recursive.include'] = Includer(
            self.application, environ, start_response)
        return self.application(environ, start_response)

class Recursive(object):

    def __init__(self, application, environ, start_response):
        self.application = application
        self.original_environ = environ.copy()
        self.previous_environ = environ
        self.start_response = start_response

    def __call__(self, path, new_environ=None):
        environ = self.original_environ.copy()
        if new_environ:
            environ.update(new_environ)
        environ['paste.recursive.previous_environ'] = self.previous_environ
        base_path = self.original_environ.get('SCRIPT_NAME')
        if path.startswith('/'):
            assert path.startswith(base_path), "You can only forward requests to resources under the path %r (not %r)" % (base_path, path)
            path = path[len(base_path)+1:]
        assert not path.startswith('/')
        path_info = '/' + path
        environ['PATH_INFO'] = path_info
        return self.activate(environ)

class Forwarder(Recursive):

    def activate(self, environ):
        environ['wsgi.errors'].write('Forwarding to %r\n' % (environ['SCRIPT_NAME'] + environ['PATH_INFO']))
        return self.application(environ, self.start_response)

class Includer(Recursive):
    
    def activate(self, environ):
        environ['wsgi.errors'].write('Including %r\n' % (environ['SCRIPT_NAME'] + environ['PATH_INFO']))
        response = IncludedResponse
        def start_response(status, headers):
            response.status = status
            response.headers = headers
            return response.write
        app_iter = self.application(environ, start_response)
        try:
            for s in app_iter:
                response.write(s)
        finally:
            if hasattr(app_iter, 'close'):
                app_iter.close()
        response.close()
        return response

class IncludedResponse(object):

    def __init__(self):
        self.headers = None
        self.status = None
        self.output = StringIO()
        self.str = None

    def close(self):
        self.str = self.output.getvalue()
        self.output.close()
        self.output = None

    def write(self):
        assert self.output is not None, "This response has already been closed and no further data can be written."
        self.output.write()

    def __str__(self):
        return self.body

    def body__get(self):
        if self.str is None:
            return self.output.getvalue()
        else:
            return self.str
    body = property(body__get)