/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/smart/request.py

Merge bzr.dev r3466

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
"""Basic server-side logic for dealing with requests."""
18
 
 
 
17
"""Basic server-side logic for dealing with requests.
 
18
 
 
19
**XXX**:
 
20
 
 
21
The class names are a little confusing: the protocol will instantiate a
 
22
SmartServerRequestHandler, whose dispatch_command method creates an instance of
 
23
a SmartServerRequest subclass.
 
24
 
 
25
The request_handlers registry tracks SmartServerRequest classes (rather than
 
26
SmartServerRequestHandler).
 
27
"""
19
28
 
20
29
import tempfile
21
30
 
24
33
    errors,
25
34
    registry,
26
35
    revision,
 
36
    urlutils,
27
37
    )
28
38
from bzrlib.bundle.serializer import write_bundle
29
39
 
30
40
 
31
41
class SmartServerRequest(object):
32
 
    """Base class for request handlers."""
 
42
    """Base class for request handlers.
 
43
    
 
44
    To define a new request, subclass this class and override the `do` method
 
45
    (and if appropriate, `do_body` as well).  Request implementors should take
 
46
    care to call `translate_client_path` and `transport_from_client_path` as
 
47
    appropriate when dealing with paths received from the client.
 
48
    """
 
49
    # XXX: rename this class to BaseSmartServerRequestHandler ?  A request
 
50
    # *handler* is a different concept to the request.
33
51
 
34
 
    def __init__(self, backing_transport):
 
52
    def __init__(self, backing_transport, root_client_path='/'):
35
53
        """Constructor.
36
54
 
37
55
        :param backing_transport: the base transport to be used when performing
38
56
            this request.
 
57
        :param root_client_path: the client path that maps to the root of
 
58
            backing_transport.  This is used to interpret relpaths received
 
59
            from the client.  Clients will not be able to refer to paths above
 
60
            this root.  If root_client_path is None, then no translation will
 
61
            be performed on client paths.  Default is '/'.
39
62
        """
40
63
        self._backing_transport = backing_transport
 
64
        if root_client_path is not None:
 
65
            if not root_client_path.startswith('/'):
 
66
                root_client_path = '/' + root_client_path
 
67
            if not root_client_path.endswith('/'):
 
68
                root_client_path += '/'
 
69
        self._root_client_path = root_client_path
41
70
 
42
71
    def _check_enabled(self):
43
72
        """Raises DisabledMethod if this method is disabled."""
71
100
        
72
101
        Must return a SmartServerResponse.
73
102
        """
74
 
        # TODO: if a client erroneously sends a request that shouldn't have a
75
 
        # body, what to do?  Probably SmartServerRequestHandler should catch
76
 
        # this NotImplementedError and translate it into a 'bad request' error
77
 
        # to send to the client.
78
103
        raise NotImplementedError(self.do_body)
79
104
 
 
105
    def do_chunk(self, chunk_bytes):
 
106
        """Called with each body chunk if the request has a streamed body.
 
107
 
 
108
        The do() method is still called, and must have returned None.
 
109
        """
 
110
        raise NotImplementedError(self.do_chunk)
 
111
 
 
112
    def do_end(self):
 
113
        """Called when the end of the request has been received."""
 
114
        pass
 
115
    
 
116
    def translate_client_path(self, client_path):
 
117
        """Translate a path received from a network client into a local
 
118
        relpath.
 
119
 
 
120
        All paths received from the client *must* be translated.
 
121
 
 
122
        :param client_path: the path from the client.
 
123
        :returns: a relpath that may be used with self._backing_transport
 
124
            (unlike the untranslated client_path, which must not be used with
 
125
            the backing transport).
 
126
        """
 
127
        if self._root_client_path is None:
 
128
            # no translation necessary!
 
129
            return client_path
 
130
        if not client_path.startswith('/'):
 
131
            client_path = '/' + client_path
 
132
        if client_path.startswith(self._root_client_path):
 
133
            path = client_path[len(self._root_client_path):]
 
134
            relpath = urlutils.joinpath('/', path)
 
135
            if not relpath.startswith('/'):
 
136
                raise ValueError(relpath)
 
137
            return '.' + relpath
 
138
        else:
 
139
            raise errors.PathNotChild(client_path, self._root_client_path)
 
140
 
 
141
    def transport_from_client_path(self, client_path):
 
142
        """Get a backing transport corresponding to the location referred to by
 
143
        a network client.
 
144
 
 
145
        :seealso: translate_client_path
 
146
        :returns: a transport cloned from self._backing_transport
 
147
        """
 
148
        relpath = self.translate_client_path(client_path)
 
149
        return self._backing_transport.clone(relpath)
 
150
 
80
151
 
81
152
class SmartServerResponse(object):
82
153
    """A response to a client request.
108
179
                other.body_stream is self.body_stream)
109
180
 
110
181
    def __repr__(self):
111
 
        return ("<SmartServerResponse successful=%s args=%r body=%r>"
112
 
                % (self.is_successful(), self.args, self.body))
 
182
        status = {True: 'OK', False: 'ERR'}[self.is_successful()]
 
183
        return "<SmartServerResponse status=%s args=%r body=%r>" % (status,
 
184
            self.args, self.body)
113
185
 
114
186
 
115
187
class FailedSmartServerResponse(SmartServerResponse):
145
217
    # TODO: Better way of representing the body for commands that take it,
146
218
    # and allow it to be streamed into the server.
147
219
 
148
 
    def __init__(self, backing_transport, commands):
 
220
    def __init__(self, backing_transport, commands, root_client_path):
149
221
        """Constructor.
150
222
 
151
223
        :param backing_transport: a Transport to handle requests for.
153
225
            subclasses. e.g. bzrlib.transport.smart.vfs.vfs_commands.
154
226
        """
155
227
        self._backing_transport = backing_transport
 
228
        self._root_client_path = root_client_path
156
229
        self._commands = commands
157
230
        self._body_bytes = ''
158
231
        self.response = None
181
254
        try:
182
255
            command = self._commands.get(cmd)
183
256
        except LookupError:
184
 
            raise errors.SmartProtocolError("bad request %r" % (cmd,))
185
 
        self._command = command(self._backing_transport)
 
257
            raise errors.UnknownSmartMethod(cmd)
 
258
        self._command = command(self._backing_transport, self._root_client_path)
186
259
        self._run_handler_code(self._command.execute, args, {})
187
260
 
188
261
    def _run_handler_code(self, callable, args, kwargs):
234
307
            else:
235
308
                raise
236
309
 
 
310
    def headers_received(self, headers):
 
311
        # Just a no-op at the moment.
 
312
        pass
 
313
 
 
314
    def args_received(self, args):
 
315
        cmd = args[0]
 
316
        args = args[1:]
 
317
        try:
 
318
            command = self._commands.get(cmd)
 
319
        except LookupError:
 
320
            raise errors.UnknownSmartMethod(cmd)
 
321
        self._command = command(self._backing_transport)
 
322
        self._run_handler_code(self._command.execute, args, {})
 
323
 
 
324
    def prefixed_body_received(self, body_bytes):
 
325
        """No more body data will be received."""
 
326
        self._run_handler_code(self._command.do_body, (body_bytes,), {})
 
327
        # cannot read after this.
 
328
        self.finished_reading = True
 
329
 
 
330
    def body_chunk_received(self, chunk_bytes):
 
331
        self._run_handler_code(self._command.do_chunk, (chunk_bytes,), {})
 
332
 
 
333
    def end_received(self):
 
334
        self._run_handler_code(self._command.do_end, (), {})
 
335
 
237
336
 
238
337
class HelloRequest(SmartServerRequest):
239
338
    """Answer a version request with the highest protocol version this server
249
348
 
250
349
    def do(self, path, revision_id):
251
350
        # open transport relative to our base
252
 
        t = self._backing_transport.clone(path)
 
351
        t = self.transport_from_client_path(path)
253
352
        control, extra_path = bzrdir.BzrDir.open_containing_from_transport(t)
254
353
        repo = control.open_repository()
255
354
        tmpf = tempfile.TemporaryFile()
284
383
request_handlers.register_lazy(
285
384
    'Branch.set_last_revision', 'bzrlib.smart.branch', 'SmartServerBranchRequestSetLastRevision')
286
385
request_handlers.register_lazy(
 
386
    'Branch.set_last_revision_info', 'bzrlib.smart.branch',
 
387
    'SmartServerBranchRequestSetLastRevisionInfo')
 
388
request_handlers.register_lazy(
287
389
    'Branch.unlock', 'bzrlib.smart.branch', 'SmartServerBranchRequestUnlock')
288
390
request_handlers.register_lazy(
289
391
    'BzrDir.find_repository', 'bzrlib.smart.bzrdir', 'SmartServerRequestFindRepositoryV1')