/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

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

  • Committer: John Arbash Meinel
  • Date: 2008-08-18 22:34:21 UTC
  • mto: (3606.5.6 1.6)
  • mto: This revision was merged to the branch mainline in revision 3641.
  • Revision ID: john@arbash-meinel.com-20080818223421-todjny24vj4faj4t
Add tests for the fetching behavior.

The proper parameter passed is 'unordered' add an assert for it, and
fix callers that were passing 'unsorted' instead.
Add tests that we make the right get_record_stream call based
on the value of _fetch_uses_deltas.
Fix the fetch request for signatures.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
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
 
16
 
 
17
"""WSGI application for bzr HTTP smart server.
 
18
 
 
19
For more information about WSGI, see PEP 333:
 
20
    http://www.python.org/dev/peps/pep-0333/
 
21
"""
 
22
 
 
23
from cStringIO import StringIO
 
24
 
 
25
from bzrlib.smart import protocol, medium
 
26
from bzrlib.transport import chroot, get_transport
 
27
from bzrlib.urlutils import local_path_to_url
 
28
    
 
29
 
 
30
def make_app(root, prefix, path_var='REQUEST_URI', readonly=True):
 
31
    """Convenience function to construct a WSGI bzr smart server.
 
32
    
 
33
    :param root: a local path that requests will be relative to.
 
34
    :param prefix: See RelpathSetter.
 
35
    :param path_var: See RelpathSetter.
 
36
    """
 
37
    local_url = local_path_to_url(root)
 
38
    if readonly:
 
39
        base_transport = get_transport('readonly+' + local_url)
 
40
    else:
 
41
        base_transport = get_transport(local_url)
 
42
    app = SmartWSGIApp(base_transport, prefix)
 
43
    app = RelpathSetter(app, '', path_var)
 
44
    return app
 
45
 
 
46
 
 
47
class RelpathSetter(object):
 
48
    """WSGI middleware to set 'bzrlib.relpath' in the environ.
 
49
    
 
50
    Different servers can invoke a SmartWSGIApp in different ways.  This
 
51
    middleware allows an adminstrator to configure how to the SmartWSGIApp will
 
52
    determine what path it should be serving for a given request for many common
 
53
    situations.
 
54
 
 
55
    For example, a request for "/some/prefix/repo/branch/.bzr/smart" received by
 
56
    a typical Apache and mod_fastcgi configuration will set `REQUEST_URI` to
 
57
    "/some/prefix/repo/branch/.bzr/smart".  A RelpathSetter with
 
58
    prefix="/some/prefix/" and path_var="REQUEST_URI" will set that request's
 
59
    'bzrlib.relpath' variable to "repo/branch".
 
60
    """
 
61
    
 
62
    def __init__(self, app, prefix='', path_var='REQUEST_URI'):
 
63
        """Constructor.
 
64
 
 
65
        :param app: WSGI app to wrap, e.g. a SmartWSGIApp instance.
 
66
        :param path_var: the variable in the WSGI environ to calculate the
 
67
            'bzrlib.relpath' variable from.
 
68
        :param prefix: a prefix to strip from the variable specified in
 
69
            path_var before setting 'bzrlib.relpath'.
 
70
        """
 
71
        self.app = app
 
72
        self.prefix = prefix
 
73
        self.path_var = path_var
 
74
 
 
75
    def __call__(self, environ, start_response):
 
76
        path = environ[self.path_var]
 
77
        suffix = '/.bzr/smart'
 
78
        if not (path.startswith(self.prefix) and path.endswith(suffix)):
 
79
            start_response('404 Not Found', [])
 
80
            return []
 
81
        environ['bzrlib.relpath'] = path[len(self.prefix):-len(suffix)]
 
82
        return self.app(environ, start_response)
 
83
 
 
84
 
 
85
class SmartWSGIApp(object):
 
86
    """A WSGI application for the bzr smart server."""
 
87
 
 
88
    def __init__(self, backing_transport, root_client_path='/'):
 
89
        """Constructor.
 
90
 
 
91
        :param backing_transport: a transport.  Requests will be processed
 
92
            relative to this transport.
 
93
        :param root_client_path: the client path that maps to the root of
 
94
            backing_transport.  This is used to interpret relpaths received from
 
95
            the client.
 
96
        """
 
97
        # Use a ChrootTransportDecorator so that this web application won't
 
98
        # accidentally let people access locations they shouldn't.
 
99
        # e.g. consider a smart server request for "get /etc/passwd" or
 
100
        # something.
 
101
        self.chroot_server = chroot.ChrootServer(backing_transport)
 
102
        self.chroot_server.setUp()
 
103
        self.backing_transport = get_transport(self.chroot_server.get_url())
 
104
        self.root_client_path = root_client_path
 
105
        # While the chroot server can technically be torn down at this point,
 
106
        # as all it does is remove the scheme registration from transport's 
 
107
        # protocol dictionary, we don't *just in case* there are parts of 
 
108
        # bzrlib that will invoke 'get_transport' on urls rather than cloning
 
109
        # around the existing transport.
 
110
        #self.chroot_server.tearDown()
 
111
 
 
112
    def __call__(self, environ, start_response):
 
113
        """WSGI application callable."""
 
114
        if environ['REQUEST_METHOD'] != 'POST':
 
115
            start_response('405 Method not allowed', [('Allow', 'POST')])
 
116
            return []
 
117
 
 
118
        relpath = environ['bzrlib.relpath']
 
119
 
 
120
        if not relpath.startswith('/'):
 
121
            relpath = '/' + relpath
 
122
        if not relpath.endswith('/'):
 
123
            relpath += '/'
 
124
 
 
125
        # Compare the HTTP path (relpath) and root_client_path, and calculate
 
126
        # new relpath and root_client_path accordingly, to be used to build the
 
127
        # request.
 
128
        if relpath.startswith(self.root_client_path):
 
129
            # The relpath traverses all of the mandatory root client path.
 
130
            # Remove the root_client_path from the relpath, and set
 
131
            # adjusted_tcp to None to tell the request handler that no further
 
132
            # path translation is required.
 
133
            adjusted_rcp = None
 
134
            adjusted_relpath = relpath[len(self.root_client_path):]
 
135
        elif self.root_client_path.startswith(relpath):
 
136
            # The relpath traverses some of the mandatory root client path.
 
137
            # Subtract the relpath from the root_client_path, and set the
 
138
            # relpath to '.'.
 
139
            adjusted_rcp = '/' + self.root_client_path[len(relpath):]
 
140
            adjusted_relpath = '.'
 
141
        else:
 
142
            adjusted_rcp = self.root_client_path
 
143
            adjusted_relpath = relpath
 
144
 
 
145
        if adjusted_relpath.startswith('/'):
 
146
            adjusted_relpath = adjusted_relpath[1:]
 
147
        if adjusted_relpath.startswith('/'):
 
148
            raise AssertionError(adjusted_relpath)
 
149
 
 
150
        transport = self.backing_transport.clone(adjusted_relpath)
 
151
        out_buffer = StringIO()
 
152
        request_data_length = int(environ['CONTENT_LENGTH'])
 
153
        request_data_bytes = environ['wsgi.input'].read(request_data_length)
 
154
        smart_protocol_request = self.make_request(
 
155
            transport, out_buffer.write, request_data_bytes, adjusted_rcp)
 
156
        if smart_protocol_request.next_read_size() != 0:
 
157
            # The request appears to be incomplete, or perhaps it's just a
 
158
            # newer version we don't understand.  Regardless, all we can do
 
159
            # is return an error response in the format of our version of the
 
160
            # protocol.
 
161
            response_data = 'error\x01incomplete request\n'
 
162
        else:
 
163
            response_data = out_buffer.getvalue()
 
164
        headers = [('Content-type', 'application/octet-stream')]
 
165
        headers.append(("Content-Length", str(len(response_data))))
 
166
        start_response('200 OK', headers)
 
167
        return [response_data]
 
168
 
 
169
    def make_request(self, transport, write_func, request_bytes, rcp):
 
170
        protocol_factory, unused_bytes = medium._get_protocol_factory_for_bytes(
 
171
            request_bytes)
 
172
        server_protocol = protocol_factory(transport, write_func, rcp)
 
173
        server_protocol.accept_bytes(unused_bytes)
 
174
        return server_protocol