31
32
from bzrlib import (
36
38
from bzrlib.smart.protocol import (
39
MESSAGE_VERSION_THREE,
37
40
REQUEST_VERSION_TWO,
38
41
SmartClientRequestProtocolOne,
39
42
SmartServerRequestProtocolOne,
40
43
SmartServerRequestProtocolTwo,
44
build_server_protocol_three
42
46
from bzrlib.transport import ssh
49
def _get_protocol_factory_for_bytes(bytes):
50
"""Determine the right protocol factory for 'bytes'.
52
This will return an appropriate protocol factory depending on the version
53
of the protocol being used, as determined by inspecting the given bytes.
54
The bytes should have at least one newline byte (i.e. be a whole line),
55
otherwise it's possible that a request will be incorrectly identified as
58
Typical use would be::
60
factory, unused_bytes = _get_protocol_factory_for_bytes(bytes)
61
server_protocol = factory(transport, write_func, root_client_path)
62
server_protocol.accept_bytes(unused_bytes)
64
:param bytes: a str of bytes of the start of the request.
65
:returns: 2-tuple of (protocol_factory, unused_bytes). protocol_factory is
66
a callable that takes three args: transport, write_func,
67
root_client_path. unused_bytes are any bytes that were not part of a
68
protocol version marker.
70
if bytes.startswith(MESSAGE_VERSION_THREE):
71
protocol_factory = build_server_protocol_three
72
bytes = bytes[len(MESSAGE_VERSION_THREE):]
73
elif bytes.startswith(REQUEST_VERSION_TWO):
74
protocol_factory = SmartServerRequestProtocolTwo
75
bytes = bytes[len(REQUEST_VERSION_TWO):]
77
protocol_factory = SmartServerRequestProtocolOne
78
return protocol_factory, bytes
45
81
class SmartServerStreamMedium(object):
46
82
"""Handles smart commands coming over a stream.
78
114
This sets the _push_back_buffer to the given bytes.
80
assert self._push_back_buffer is None, (
81
"_push_back called when self._push_back_buffer is %r"
82
% (self._push_back_buffer,))
116
if self._push_back_buffer is not None:
117
raise AssertionError(
118
"_push_back called when self._push_back_buffer is %r"
119
% (self._push_back_buffer,))
85
122
self._push_back_buffer = bytes
87
124
def _get_push_back_buffer(self):
88
assert self._push_back_buffer != '', (
89
'%s._push_back_buffer should never be the empty string, '
90
'which can be confused with EOF' % (self,))
125
if self._push_back_buffer == '':
126
raise AssertionError(
127
'%s._push_back_buffer should never be the empty string, '
128
'which can be confused with EOF' % (self,))
91
129
bytes = self._push_back_buffer
92
130
self._push_back_buffer = None
115
153
:returns: a SmartServerRequestProtocol.
117
# Identify the protocol version.
118
155
bytes = self._get_line()
119
if bytes.startswith(REQUEST_VERSION_TWO):
120
protocol_class = SmartServerRequestProtocolTwo
121
bytes = bytes[len(REQUEST_VERSION_TWO):]
123
protocol_class = SmartServerRequestProtocolOne
124
protocol = protocol_class(
156
protocol_factory, unused_bytes = _get_protocol_factory_for_bytes(bytes)
157
protocol = protocol_factory(
125
158
self.backing_transport, self._write_out, self.root_client_path)
126
protocol.accept_bytes(bytes)
159
protocol.accept_bytes(unused_bytes)
129
162
def _serve_one_request(self, protocol):
193
226
protocol.accept_bytes(bytes)
195
self._push_back(protocol.excess_buffer)
228
self._push_back(protocol.unused_data)
197
230
def _get_bytes(self, desired_count):
198
231
if self._push_back_buffer is not None:
202
235
return self.socket.recv(4096)
204
237
def terminate_due_to_error(self):
205
"""Called when an unhandled exception from the protocol occurs."""
206
238
# TODO: This should log to a server log file, but no such thing
207
239
# exists yet. Andrew Bennetts 2006-09-29.
208
240
self.socket.close()
401
433
class SmartClientMedium(object):
402
434
"""Smart client is a medium for sending smart protocol requests over."""
436
def __init__(self, base):
405
437
super(SmartClientMedium, self).__init__()
406
439
self._protocol_version_error = None
407
440
self._protocol_version = None
441
self._done_hello = False
442
# Be optimistic: we assume the remote end can accept new remote
443
# requests until we get an error saying otherwise. (1.2 adds some
444
# requests that send bodies, which confuses older servers.)
445
self._remote_is_at_least_1_2 = True
409
447
def protocol_version(self):
410
"""Find out the best protocol version to use."""
448
"""Find out if 'hello' smart request works."""
411
449
if self._protocol_version_error is not None:
412
450
raise self._protocol_version_error
413
if self._protocol_version is None:
451
if not self._done_hello:
415
453
medium_request = self.get_request()
416
454
# Send a 'hello' request in protocol version one, for maximum
417
455
# backwards compatibility.
418
456
client_protocol = SmartClientRequestProtocolOne(medium_request)
419
self._protocol_version = client_protocol.query_version()
457
client_protocol.query_version()
458
self._done_hello = True
420
459
except errors.SmartProtocolError, e:
421
460
# Cache the error, just like we would cache a successful
423
462
self._protocol_version_error = e
425
return self._protocol_version
466
def should_probe(self):
467
"""Should RemoteBzrDirFormat.probe_transport send a smart request on
470
Some transports are unambiguously smart-only; there's no need to check
471
if the transport is able to carry smart requests, because that's all
472
it is for. In those cases, this method should return False.
474
But some HTTP transports can sometimes fail to carry smart requests,
475
but still be usuable for accessing remote bzrdirs via plain file
476
accesses. So for those transports, their media should return True here
477
so that RemoteBzrDirFormat can determine if it is appropriate for that
427
482
def disconnect(self):
428
483
"""If this medium maintains a persistent connection, close it.
430
485
The default implementation does nothing.
488
def remote_path_from_transport(self, transport):
489
"""Convert transport into a path suitable for using in a request.
491
Note that the resulting remote path doesn't encode the host name or
492
anything but path, so it is only safe to use it in requests sent over
493
the medium from the matching transport.
495
medium_base = urlutils.join(self.base, '/')
496
rel_url = urlutils.relative_url(medium_base, transport.base)
497
return urllib.unquote(rel_url)
434
500
class SmartClientStreamMedium(SmartClientMedium):
435
501
"""Stream based medium common class.
444
SmartClientMedium.__init__(self)
509
def __init__(self, base):
510
SmartClientMedium.__init__(self, base)
445
511
self._current_request = None
446
# Be optimistic: we assume the remote end can accept new remote
447
# requests until we get an error saying otherwise. (1.2 adds some
448
# requests that send bodies, which confuses older servers.)
449
self._remote_is_at_least_1_2 = True
451
513
def accept_bytes(self, bytes):
452
514
self._accept_bytes(bytes)
483
545
This client does not manage the pipes: it assumes they will always be open.
486
def __init__(self, readable_pipe, writeable_pipe):
487
SmartClientStreamMedium.__init__(self)
548
def __init__(self, readable_pipe, writeable_pipe, base):
549
SmartClientStreamMedium.__init__(self, base)
488
550
self._readable_pipe = readable_pipe
489
551
self._writeable_pipe = writeable_pipe
505
567
"""A client medium using SSH."""
507
569
def __init__(self, host, port=None, username=None, password=None,
508
vendor=None, bzr_remote_path=None):
570
base=None, vendor=None, bzr_remote_path=None):
509
571
"""Creates a client that will connect on the first use.
511
573
:param vendor: An optional override for the ssh vendor to use. See
512
574
bzrlib.transport.ssh for details on ssh vendors.
514
SmartClientStreamMedium.__init__(self)
576
SmartClientStreamMedium.__init__(self, base)
515
577
self._connected = False
516
578
self._host = host
517
579
self._password = password
577
639
class SmartTCPClientMedium(SmartClientStreamMedium):
578
640
"""A client medium using TCP."""
580
def __init__(self, host, port):
642
def __init__(self, host, port, base):
581
643
"""Creates a client that will connect on the first use."""
582
SmartClientStreamMedium.__init__(self)
644
SmartClientStreamMedium.__init__(self, base)
583
645
self._connected = False
584
646
self._host = host
585
647
self._port = port
663
725
This clears the _current_request on self._medium to allow a new
664
726
request to be created.
666
assert self._medium._current_request is self
728
if self._medium._current_request is not self:
729
raise AssertionError()
667
730
self._medium._current_request = None
669
732
def _finished_writing(self):