/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: Vincent Ladeuil
  • Date: 2012-01-18 14:09:19 UTC
  • mto: This revision was merged to the branch mainline in revision 6468.
  • Revision ID: v.ladeuil+lp@free.fr-20120118140919-rlvdrhpc0nq1lbwi
Change set/remove to require a lock for the branch config files.

This means that tests (or any plugin for that matter) do not requires an
explicit lock on the branch anymore to change a single option. This also
means the optimisation becomes "opt-in" and as such won't be as
spectacular as it may be and/or harder to get right (nothing fails
anymore).

This reduces the diff by ~300 lines.

Code/tests that were updating more than one config option is still taking
a lock to at least avoid some IOs and demonstrate the benefits through
the decreased number of hpss calls.

The duplication between BranchStack and BranchOnlyStack will be removed
once the same sharing is in place for local config files, at which point
the Stack class itself may be able to host the changes.

Show diffs side-by-side

added added

removed removed

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