~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/http/wsgi.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-05-04 12:10:51 UTC
  • mfrom: (5819.1.4 777007-developer-doc)
  • Revision ID: pqm@pqm.ubuntu.com-20110504121051-aovlsmqiivjmc4fc
(jelmer) Small fixes to developer documentation. (Jonathan Riddell)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 Canonical Ltd
 
1
# Copyright (C) 2006-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""WSGI application for bzr HTTP smart server.
18
18
 
22
22
 
23
23
from cStringIO import StringIO
24
24
 
25
 
from bzrlib.transport import chroot, get_transport, smart
 
25
from bzrlib.smart import protocol, medium
 
26
from bzrlib.transport import chroot, get_transport
26
27
from bzrlib.urlutils import local_path_to_url
27
 
    
28
 
 
29
 
def make_app(root, prefix, path_var):
 
28
 
 
29
 
 
30
def make_app(root, prefix, path_var='REQUEST_URI', readonly=True,
 
31
    load_plugins=True, enable_logging=True):
30
32
    """Convenience function to construct a WSGI bzr smart server.
31
 
    
 
33
 
32
34
    :param root: a local path that requests will be relative to.
33
35
    :param prefix: See RelpathSetter.
34
36
    :param path_var: See RelpathSetter.
35
37
    """
36
 
    base_transport = get_transport('readonly+' + local_path_to_url(root))
37
 
    app = SmartWSGIApp(base_transport)
38
 
    app = RelpathSetter(app, prefix, path_var)
 
38
    local_url = local_path_to_url(root)
 
39
    if readonly:
 
40
        base_transport = get_transport('readonly+' + local_url)
 
41
    else:
 
42
        base_transport = get_transport(local_url)
 
43
    if load_plugins:
 
44
        from bzrlib.plugin import load_plugins
 
45
        load_plugins()
 
46
    if enable_logging:
 
47
        import bzrlib.trace
 
48
        bzrlib.trace.enable_default_logging()
 
49
    app = SmartWSGIApp(base_transport, prefix)
 
50
    app = RelpathSetter(app, '', path_var)
39
51
    return app
40
52
 
41
53
 
42
54
class RelpathSetter(object):
43
55
    """WSGI middleware to set 'bzrlib.relpath' in the environ.
44
 
    
 
56
 
45
57
    Different servers can invoke a SmartWSGIApp in different ways.  This
46
58
    middleware allows an adminstrator to configure how to the SmartWSGIApp will
47
59
    determine what path it should be serving for a given request for many common
53
65
    prefix="/some/prefix/" and path_var="REQUEST_URI" will set that request's
54
66
    'bzrlib.relpath' variable to "repo/branch".
55
67
    """
56
 
    
 
68
 
57
69
    def __init__(self, app, prefix='', path_var='REQUEST_URI'):
58
70
        """Constructor.
59
71
 
71
83
        path = environ[self.path_var]
72
84
        suffix = '/.bzr/smart'
73
85
        if not (path.startswith(self.prefix) and path.endswith(suffix)):
74
 
            start_response('404 Not Found', {})
 
86
            start_response('404 Not Found', [])
75
87
            return []
76
88
        environ['bzrlib.relpath'] = path[len(self.prefix):-len(suffix)]
77
89
        return self.app(environ, start_response)
80
92
class SmartWSGIApp(object):
81
93
    """A WSGI application for the bzr smart server."""
82
94
 
83
 
    def __init__(self, backing_transport):
 
95
    def __init__(self, backing_transport, root_client_path='/'):
84
96
        """Constructor.
85
97
 
86
98
        :param backing_transport: a transport.  Requests will be processed
87
99
            relative to this transport.
 
100
        :param root_client_path: the client path that maps to the root of
 
101
            backing_transport.  This is used to interpret relpaths received from
 
102
            the client.
88
103
        """
89
 
        # Use a ChrootTransportDecorator so that this web application won't
 
104
        # Use a ChrootServer so that this web application won't
90
105
        # accidentally let people access locations they shouldn't.
91
106
        # e.g. consider a smart server request for "get /etc/passwd" or
92
107
        # something.
93
 
        self.backing_transport = chroot.ChrootTransportDecorator(
94
 
            'chroot+' + backing_transport.base, _decorated=backing_transport)
 
108
        self.chroot_server = chroot.ChrootServer(backing_transport)
 
109
        self.chroot_server.start_server()
 
110
        self.backing_transport = get_transport(self.chroot_server.get_url())
 
111
        self.root_client_path = root_client_path
 
112
        # While the chroot server can technically be torn down at this point,
 
113
        # as all it does is remove the scheme registration from transport's
 
114
        # protocol dictionary, we don't *just in case* there are parts of
 
115
        # bzrlib that will invoke 'get_transport' on urls rather than cloning
 
116
        # around the existing transport.
 
117
        #self.chroot_server.stop_server()
95
118
 
96
119
    def __call__(self, environ, start_response):
97
120
        """WSGI application callable."""
100
123
            return []
101
124
 
102
125
        relpath = environ['bzrlib.relpath']
103
 
        transport = self.backing_transport.clone(relpath)
 
126
 
 
127
        if not relpath.startswith('/'):
 
128
            relpath = '/' + relpath
 
129
        if not relpath.endswith('/'):
 
130
            relpath += '/'
 
131
 
 
132
        # Compare the HTTP path (relpath) and root_client_path, and calculate
 
133
        # new relpath and root_client_path accordingly, to be used to build the
 
134
        # request.
 
135
        if relpath.startswith(self.root_client_path):
 
136
            # The relpath traverses all of the mandatory root client path.
 
137
            # Remove the root_client_path from the relpath, and set
 
138
            # adjusted_tcp to None to tell the request handler that no further
 
139
            # path translation is required.
 
140
            adjusted_rcp = None
 
141
            adjusted_relpath = relpath[len(self.root_client_path):]
 
142
        elif self.root_client_path.startswith(relpath):
 
143
            # The relpath traverses some of the mandatory root client path.
 
144
            # Subtract the relpath from the root_client_path, and set the
 
145
            # relpath to '.'.
 
146
            adjusted_rcp = '/' + self.root_client_path[len(relpath):]
 
147
            adjusted_relpath = '.'
 
148
        else:
 
149
            adjusted_rcp = self.root_client_path
 
150
            adjusted_relpath = relpath
 
151
 
 
152
        if adjusted_relpath.startswith('/'):
 
153
            adjusted_relpath = adjusted_relpath[1:]
 
154
        if adjusted_relpath.startswith('/'):
 
155
            raise AssertionError(adjusted_relpath)
 
156
 
 
157
        transport = self.backing_transport.clone(adjusted_relpath)
104
158
        out_buffer = StringIO()
105
 
        smart_protocol_request = self.make_request(transport, out_buffer.write)
106
159
        request_data_length = int(environ['CONTENT_LENGTH'])
107
160
        request_data_bytes = environ['wsgi.input'].read(request_data_length)
108
 
        smart_protocol_request.accept_bytes(request_data_bytes)
 
161
        smart_protocol_request = self.make_request(
 
162
            transport, out_buffer.write, request_data_bytes, adjusted_rcp)
109
163
        if smart_protocol_request.next_read_size() != 0:
110
164
            # The request appears to be incomplete, or perhaps it's just a
111
165
            # newer version we don't understand.  Regardless, all we can do
119
173
        start_response('200 OK', headers)
120
174
        return [response_data]
121
175
 
122
 
    def make_request(self, transport, write_func):
123
 
        return smart.SmartServerRequestProtocolOne(transport, write_func)
 
176
    def make_request(self, transport, write_func, request_bytes, rcp):
 
177
        protocol_factory, unused_bytes = medium._get_protocol_factory_for_bytes(
 
178
            request_bytes)
 
179
        server_protocol = protocol_factory(
 
180
            transport, write_func, rcp, self.backing_transport)
 
181
        server_protocol.accept_bytes(unused_bytes)
 
182
        return server_protocol