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
17
"""Smart-server protocol, client and server.
19
Requests are sent as a command and list of arguments, followed by optional
20
bulk body data. Responses are similarly a response and list of arguments,
21
followed by bulk body data. ::
24
Fields are separated by Ctrl-A.
25
BULK_DATA := CHUNK+ TRAILER
26
Chunks can be repeated as many times as necessary.
27
CHUNK := CHUNK_LEN CHUNK_BODY
28
CHUNK_LEN := DIGIT+ NEWLINE
29
Gives the number of bytes in the following chunk.
30
CHUNK_BODY := BYTE[chunk_len]
31
TRAILER := SUCCESS_TRAILER | ERROR_TRAILER
32
SUCCESS_TRAILER := 'done' NEWLINE
35
Paths are passed across the network. The client needs to see a namespace that
36
includes any repository that might need to be referenced, and the client needs
37
to know about a root directory beyond which it cannot ascend.
39
Servers run over ssh will typically want to be able to access any path the user
40
can access. Public servers on the other hand (which might be over http, ssh
41
or tcp) will typically want to restrict access to only a particular directory
42
and its children, so will want to do a software virtual root at that level.
43
In other words they'll want to rewrite incoming paths to be under that level
44
(and prevent escaping using ../ tricks.)
46
URLs that include ~ should probably be passed across to the server verbatim
47
and the server can expand them. This will proably not be meaningful when
48
limited to a directory?
17
"""RemoteTransport client for the smart-server.
19
This module shouldn't be accessed directly. The classes defined here should be
20
imported from bzrlib.smart.
52
# TODO: _translate_error should be on the client, not the transport because
53
# error coding is wire protocol specific.
55
# TODO: A plain integer from query_version is too simple; should give some
58
# TODO: Server should probably catch exceptions within itself and send them
59
# back across the network. (But shouldn't catch KeyboardInterrupt etc)
60
# Also needs to somehow report protocol errors like bad requests. Need to
61
# consider how we'll handle error reporting, e.g. if we get halfway through a
62
# bulk transfer and then something goes wrong.
64
# TODO: Standard marker at start of request/response lines?
66
# TODO: Make each request and response self-validatable, e.g. with checksums.
68
# TODO: get/put objects could be changed to gradually read back the data as it
69
# comes across the network
71
# TODO: What should the server do if it hits an error and has to terminate?
73
# TODO: is it useful to allow multiple chunks in the bulk data?
75
# TODO: If we get an exception during transmission of bulk data we can't just
76
# emit the exception because it won't be seen.
77
# John proposes: I think it would be worthwhile to have a header on each
78
# chunk, that indicates it is another chunk. Then you can send an 'error'
79
# chunk as long as you finish the previous chunk.
81
# TODO: Clone method on Transport; should work up towards parent directory;
82
# unclear how this should be stored or communicated to the server... maybe
83
# just pass it on all relevant requests?
85
# TODO: Better name than clone() for changing between directories. How about
86
# open_dir or change_dir or chdir?
88
# TODO: Is it really good to have the notion of current directory within the
89
# connection? Perhaps all Transports should factor out a common connection
90
# from the thing that has the directory context?
92
# TODO: Pull more things common to sftp and ssh to a higher level.
94
# TODO: The server that manages a connection should be quite small and retain
95
# minimum state because each of the requests are supposed to be stateless.
96
# Then we can write another implementation that maps to http.
98
# TODO: What to do when a client connection is garbage collected? Maybe just
99
# abruptly drop the connection?
101
# TODO: Server in some cases will need to restrict access to files outside of
102
# a particular root directory. LocalTransport doesn't do anything to stop you
103
# ascending above the base directory, so we need to prevent paths
104
# containing '..' in either the server or transport layers. (Also need to
105
# consider what happens if someone creates a symlink pointing outside the
108
# TODO: Server should rebase absolute paths coming across the network to put
109
# them under the virtual root, if one is in use. LocalTransport currently
110
# doesn't do that; if you give it an absolute path it just uses it.
112
# XXX: Arguments can't contain newlines or ascii; possibly we should e.g.
113
# urlescape them instead. Indeed possibly this should just literally be
116
# FIXME: This transport, with several others, has imperfect handling of paths
117
# within urls. It'd probably be better for ".." from a root to raise an error
118
# rather than return the same directory as we do at present.
120
# TODO: Rather than working at the Transport layer we want a Branch,
121
# Repository or BzrDir objects that talk to a server.
123
# TODO: Probably want some way for server commands to gradually produce body
124
# data rather than passing it as a string; they could perhaps pass an
125
# iterator-like callback that will gradually yield data; it probably needs a
126
# close() method that will always be closed to do any necessary cleanup.
128
# TODO: Split the actual smart server from the ssh encoding of it.
130
# TODO: Perhaps support file-level readwrite operations over the transport
133
# TODO: SmartBzrDir class, proxying all Branch etc methods across to another
134
# branch doing file-level operations.
136
# TODO: jam 20060915 _decode_tuple is acting directly on input over
137
# the socket, and it assumes everything is UTF8 sections separated
138
# by \001. Which means a request like '\002' Will abort the connection
139
# because of a UnicodeDecodeError. It does look like invalid data will
140
# kill the SmartStreamServer, but only with an abort + exception, and
141
# the overall server shouldn't die.
23
__all__ = ['RemoteTransport', 'RemoteTCPTransport', 'RemoteSSHTransport']
143
25
from cStringIO import StringIO
153
29
from bzrlib import (
161
from bzrlib.bundle.serializer import write_bundle
162
from bzrlib.trace import mutter
163
from bzrlib.transport import local
34
from bzrlib.smart import client, medium, protocol
165
36
# must do this otherwise urllib can't parse the urls properly :(
166
for scheme in ['ssh', 'bzr', 'bzr+loopback', 'bzr+ssh']:
37
for scheme in ['ssh', 'bzr', 'bzr+loopback', 'bzr+ssh', 'bzr+http']:
167
38
transport.register_urlparse_netloc_protocol(scheme)
171
def _recv_tuple(from_file):
172
req_line = from_file.readline()
173
return _decode_tuple(req_line)
176
def _decode_tuple(req_line):
177
if req_line == None or req_line == '':
179
if req_line[-1] != '\n':
180
raise errors.SmartProtocolError("request %r not terminated" % req_line)
181
return tuple((a.decode('utf-8') for a in req_line[:-1].split('\x01')))
184
def _encode_tuple(args):
185
"""Encode the tuple args to a bytestream."""
186
return '\x01'.join((a.encode('utf-8') for a in args)) + '\n'
189
class SmartProtocolBase(object):
190
"""Methods common to client and server"""
192
def _send_bulk_data(self, body):
193
"""Send chunked body data"""
194
assert isinstance(body, str)
195
bytes = ''.join(('%d\n' % len(body), body, 'done\n'))
196
self._write_and_flush(bytes)
198
# TODO: this only actually accomodates a single block; possibly should support
200
def _recv_bulk(self):
201
chunk_len = self._in.readline()
203
chunk_len = int(chunk_len)
205
raise errors.SmartProtocolError("bad chunk length line %r" % chunk_len)
206
bulk = self._in.read(chunk_len)
207
if len(bulk) != chunk_len:
208
raise errors.SmartProtocolError("short read fetching bulk data chunk")
212
def _recv_tuple(self):
213
return _recv_tuple(self._in)
215
def _recv_trailer(self):
216
resp = self._recv_tuple()
217
if resp == ('done', ):
220
self._translate_error(resp)
222
def _serialise_offsets(self, offsets):
223
"""Serialise a readv offset list."""
225
for start, length in offsets:
226
txt.append('%d,%d' % (start, length))
227
return '\n'.join(txt)
229
def _write_and_flush(self, bytes):
230
"""Write bytes to self._out and flush it."""
231
# XXX: this will be inefficient. Just ask Robert.
232
self._out.write(bytes)
236
class SmartStreamServer(SmartProtocolBase):
237
"""Handles smart commands coming over a stream.
239
The stream may be a pipe connected to sshd, or a tcp socket, or an
240
in-process fifo for testing.
242
One instance is created for each connected client; it can serve multiple
243
requests in the lifetime of the connection.
245
The server passes requests through to an underlying backing transport,
246
which will typically be a LocalTransport looking at the server's filesystem.
249
def __init__(self, in_file, out_file, backing_transport):
250
"""Construct new server.
252
:param in_file: Python file from which requests can be read.
253
:param out_file: Python file to write responses.
254
:param backing_transport: Transport for the directory served.
258
self.smart_server = SmartServer(backing_transport)
259
# server can call back to us to get bulk data - this is not really
260
# ideal, they should get it per request instead
261
self.smart_server._recv_body = self._recv_bulk
263
def _recv_tuple(self):
264
"""Read a request from the client and return as a tuple.
266
Returns None at end of file (if the client closed the connection.)
268
return _recv_tuple(self._in)
270
def _send_tuple(self, args):
271
"""Send response header"""
272
return self._write_and_flush(_encode_tuple(args))
274
def _send_error_and_disconnect(self, exception):
275
self._send_tuple(('error', str(exception)))
279
def _serve_one_request(self):
280
"""Read one request from input, process, send back a response.
282
:return: False if the server should terminate, otherwise None.
284
req_args = self._recv_tuple()
286
# client closed connection
287
return False # shutdown server
289
response = self.smart_server.dispatch_command(req_args[0], req_args[1:])
290
self._send_tuple(response.args)
291
if response.body is not None:
292
self._send_bulk_data(response.body)
293
except KeyboardInterrupt:
296
# everything else: pass to client, flush, and quit
297
self._send_error_and_disconnect(e)
301
"""Serve requests until the client disconnects."""
302
# Keep a reference to stderr because the sys module's globals get set to
303
# None during interpreter shutdown.
304
from sys import stderr
306
while self._serve_one_request() != False:
309
stderr.write("%s terminating on exception %s\n" % (self, e))
313
class SmartServerResponse(object):
314
"""Response generated by SmartServer."""
316
def __init__(self, args, body=None):
320
# XXX: TODO: Create a SmartServerRequest which will take the responsibility
321
# for delivering the data for a request. This could be done with as the
322
# StreamServer, though that would create conflation between request and response
323
# which may be undesirable.
326
class SmartServer(object):
327
"""Protocol logic for smart server.
329
This doesn't handle serialization at all, it just processes requests and
333
# IMPORTANT FOR IMPLEMENTORS: It is important that SmartServer not contain
334
# encoding or decoding logic to allow the wire protocol to vary from the
335
# object protocol: we will want to tweak the wire protocol separate from
336
# the object model, and ideally we will be able to do that without having
337
# a SmartServer subclass for each wire protocol, rather just a Protocol
340
# TODO: Better way of representing the body for commands that take it,
341
# and allow it to be streamed into the server.
343
def __init__(self, backing_transport):
344
self._backing_transport = backing_transport
347
"""Answer a version request with my version."""
348
return SmartServerResponse(('ok', '1'))
350
def do_has(self, relpath):
351
r = self._backing_transport.has(relpath) and 'yes' or 'no'
352
return SmartServerResponse((r,))
354
def do_get(self, relpath):
356
backing_bytes = self._backing_transport.get_bytes(relpath)
357
except errors.ReadError:
358
# cannot read the file
359
return SmartServerResponse(('ReadError', ))
360
return SmartServerResponse(('ok',), backing_bytes)
362
def _deserialise_optional_mode(self, mode):
363
# XXX: FIXME this should be on the protocol object.
369
def do_append(self, relpath, mode):
370
old_length = self._backing_transport.append_bytes(
371
relpath, self._recv_body(), self._deserialise_optional_mode(mode))
372
return SmartServerResponse(('appended', '%d' % old_length))
374
def do_delete(self, relpath):
375
self._backing_transport.delete(relpath)
377
def do_iter_files_recursive(self, abspath):
378
# XXX: the path handling needs some thought.
379
#relpath = self._backing_transport.relpath(abspath)
380
transport = self._backing_transport.clone(abspath)
381
filenames = transport.iter_files_recursive()
382
return SmartServerResponse(('names',) + tuple(filenames))
384
def do_list_dir(self, relpath):
385
filenames = self._backing_transport.list_dir(relpath)
386
return SmartServerResponse(('names',) + tuple(filenames))
388
def do_mkdir(self, relpath, mode):
389
self._backing_transport.mkdir(relpath,
390
self._deserialise_optional_mode(mode))
392
def do_move(self, rel_from, rel_to):
393
self._backing_transport.move(rel_from, rel_to)
395
def do_put(self, relpath, mode):
396
self._backing_transport.put_bytes(relpath,
398
self._deserialise_optional_mode(mode))
400
def _deserialise_offsets(self, text):
401
# XXX: FIXME this should be on the protocol object.
403
for line in text.split('\n'):
406
start, length = line.split(',')
407
offsets.append((int(start), int(length)))
410
def do_put_non_atomic(self, relpath, mode, create_parent, dir_mode):
411
create_parent_dir = (create_parent == 'T')
412
self._backing_transport.put_bytes_non_atomic(relpath,
414
mode=self._deserialise_optional_mode(mode),
415
create_parent_dir=create_parent_dir,
416
dir_mode=self._deserialise_optional_mode(dir_mode))
418
def do_readv(self, relpath):
419
offsets = self._deserialise_offsets(self._recv_body())
420
backing_bytes = ''.join(bytes for offset, bytes in
421
self._backing_transport.readv(relpath, offsets))
422
return SmartServerResponse(('readv',), backing_bytes)
424
def do_rename(self, rel_from, rel_to):
425
self._backing_transport.rename(rel_from, rel_to)
427
def do_rmdir(self, relpath):
428
self._backing_transport.rmdir(relpath)
430
def do_stat(self, relpath):
431
stat = self._backing_transport.stat(relpath)
432
return SmartServerResponse(('stat', str(stat.st_size), oct(stat.st_mode)))
434
def do_get_bundle(self, path, revision_id):
435
# open transport relative to our base
436
t = self._backing_transport.clone(path)
437
control, extra_path = bzrdir.BzrDir.open_containing_from_transport(t)
438
repo = control.open_repository()
439
tmpf = tempfile.TemporaryFile()
440
base_revision = revision.NULL_REVISION
441
write_bundle(repo, revision_id, base_revision, tmpf)
443
return SmartServerResponse((), tmpf.read())
445
def dispatch_command(self, cmd, args):
446
func = getattr(self, 'do_' + cmd, None)
448
raise errors.SmartProtocolError("bad request %r" % (cmd,))
452
result = SmartServerResponse(('ok',))
454
except errors.NoSuchFile, e:
455
return SmartServerResponse(('NoSuchFile', e.path))
456
except errors.FileExists, e:
457
return SmartServerResponse(('FileExists', e.path))
458
except errors.DirectoryNotEmpty, e:
459
return SmartServerResponse(('DirectoryNotEmpty', e.path))
460
except errors.ShortReadvError, e:
461
return SmartServerResponse(('ShortReadvError',
462
e.path, str(e.offset), str(e.length), str(e.actual)))
463
except UnicodeError, e:
464
# If it is a DecodeError, than most likely we are starting
465
# with a plain string
466
str_or_unicode = e.object
467
if isinstance(str_or_unicode, unicode):
468
val = u'u:' + str_or_unicode
470
val = u's:' + str_or_unicode.encode('base64')
471
# This handles UnicodeEncodeError or UnicodeDecodeError
472
return SmartServerResponse((e.__class__.__name__,
473
e.encoding, val, str(e.start), str(e.end), e.reason))
474
except errors.TransportNotPossible, e:
475
if e.msg == "readonly transport":
476
return SmartServerResponse(('ReadOnlyError', ))
481
class SmartTCPServer(object):
482
"""Listens on a TCP socket and accepts connections from smart clients"""
484
def __init__(self, backing_transport=None, host='127.0.0.1', port=0):
485
"""Construct a new server.
487
To actually start it running, call either start_background_thread or
490
:param host: Name of the interface to listen on.
491
:param port: TCP port to listen on, or 0 to allocate a transient port.
493
if backing_transport is None:
494
backing_transport = memory.MemoryTransport()
495
self._server_socket = socket.socket()
496
self._server_socket.bind((host, port))
497
self.port = self._server_socket.getsockname()[1]
498
self._server_socket.listen(1)
499
self._server_socket.settimeout(1)
500
self.backing_transport = backing_transport
503
# let connections timeout so that we get a chance to terminate
504
# Keep a reference to the exceptions we want to catch because the socket
505
# module's globals get set to None during interpreter shutdown.
506
from socket import timeout as socket_timeout
507
from socket import error as socket_error
508
self._should_terminate = False
509
while not self._should_terminate:
511
self.accept_and_serve()
512
except socket_timeout:
513
# just check if we're asked to stop
515
except socket_error, e:
516
trace.warning("client disconnected: %s", e)
520
"""Return the url of the server"""
521
return "bzr://%s:%d/" % self._server_socket.getsockname()
523
def accept_and_serve(self):
524
conn, client_addr = self._server_socket.accept()
525
# For WIN32, where the timeout value from the listening socket
526
# propogates to the newly accepted socket.
527
conn.setblocking(True)
528
conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
529
from_client = conn.makefile('r')
530
to_client = conn.makefile('w')
531
handler = SmartStreamServer(from_client, to_client,
532
self.backing_transport)
533
connection_thread = threading.Thread(None, handler.serve, name='smart-server-child')
534
connection_thread.setDaemon(True)
535
connection_thread.start()
537
def start_background_thread(self):
538
self._server_thread = threading.Thread(None,
540
name='server-' + self.get_url())
541
self._server_thread.setDaemon(True)
542
self._server_thread.start()
544
def stop_background_thread(self):
545
self._should_terminate = True
546
# self._server_socket.close()
547
# we used to join the thread, but it's not really necessary; it will
549
## self._server_thread.join()
552
class SmartTCPServer_for_testing(SmartTCPServer):
553
"""Server suitable for use by transport tests.
555
This server is backed by the process's cwd.
559
self._homedir = os.getcwd()
560
# The server is set up by default like for ssh access: the client
561
# passes filesystem-absolute paths; therefore the server must look
562
# them up relative to the root directory. it might be better to act
563
# a public server and have the server rewrite paths into the test
565
SmartTCPServer.__init__(self, transport.get_transport("file:///"))
568
"""Set up server for testing"""
569
self.start_background_thread()
572
self.stop_background_thread()
575
"""Return the url of the server"""
576
host, port = self._server_socket.getsockname()
577
# XXX: I think this is likely to break on windows -- self._homedir will
578
# have backslashes (and maybe a drive letter?).
579
# -- Andrew Bennetts, 2006-08-29
580
return "bzr://%s:%d%s" % (host, port, urlutils.escape(self._homedir))
582
def get_bogus_url(self):
583
"""Return a URL which will fail to connect"""
584
return 'bzr://127.0.0.1:1/'
587
class SmartStat(object):
42
# Port 4155 is the default port for bzr://, registered with IANA.
43
BZR_DEFAULT_INTERFACE = '0.0.0.0'
44
BZR_DEFAULT_PORT = 4155
47
class _SmartStat(object):
589
49
def __init__(self, size, mode):
590
50
self.st_size = size
591
51
self.st_mode = mode
594
class SmartTransport(transport.Transport):
54
class RemoteTransport(transport.Transport):
595
55
"""Connection to a smart server.
597
The connection holds references to pipes that can be used to send requests
57
The connection holds references to the medium that can be used to send
58
requests to the server.
600
60
The connection has a notion of the current directory to which it's
601
61
connected; this is incorporated in filenames passed to the server.
927
430
def list_dir(self, relpath):
928
resp = self._client._call('list_dir',
929
self._remote_path(relpath))
431
resp = self._call2('list_dir', self._remote_path(relpath))
930
432
if resp[0] == 'names':
931
433
return [name.encode('ascii') for name in resp[1:]]
933
435
self._translate_error(resp)
935
437
def iter_files_recursive(self):
936
resp = self._client._call('iter_files_recursive',
937
self._remote_path(''))
438
resp = self._call2('iter_files_recursive', self._remote_path(''))
938
439
if resp[0] == 'names':
941
442
self._translate_error(resp)
944
class SmartStreamClient(SmartProtocolBase):
945
"""Connection to smart server over two streams"""
947
def __init__(self, connect_func):
948
self._connect_func = connect_func
949
self._connected = False
954
def _ensure_connection(self):
955
if not self._connected:
956
self._in, self._out = self._connect_func()
957
self._connected = True
959
def _send_tuple(self, args):
960
self._ensure_connection()
961
return self._write_and_flush(_encode_tuple(args))
963
def _send_bulk_data(self, body):
964
self._ensure_connection()
965
SmartProtocolBase._send_bulk_data(self, body)
967
def _recv_bulk(self):
968
self._ensure_connection()
969
return SmartProtocolBase._recv_bulk(self)
971
def _recv_tuple(self):
972
self._ensure_connection()
973
return SmartProtocolBase._recv_tuple(self)
975
def _recv_trailer(self):
976
self._ensure_connection()
977
return SmartProtocolBase._recv_trailer(self)
979
def disconnect(self):
980
"""Close connection to the server"""
985
def _call(self, *args):
986
self._send_tuple(args)
987
return self._recv_tuple()
989
def _call_with_upload(self, method, args, body):
990
"""Call an rpc, supplying bulk upload data.
992
:param method: method name to call
993
:param args: parameter args tuple
994
:param body: upload body as a byte string
996
self._send_tuple((method,) + args)
997
self._send_bulk_data(body)
998
return self._recv_tuple()
1000
def query_version(self):
1001
"""Return protocol version number of the server."""
1002
# XXX: should make sure it's empty
1003
self._send_tuple(('hello',))
1004
resp = self._recv_tuple()
1005
if resp == ('ok', '1'):
445
class RemoteTCPTransport(RemoteTransport):
446
"""Connection to smart server over plain tcp.
448
This is essentially just a factory to get 'RemoteTransport(url,
449
SmartTCPClientMedium).
452
def __init__(self, url):
453
_scheme, _username, _password, _host, _port, _path = \
454
transport.split_url(url)
456
_port = BZR_DEFAULT_PORT
1008
raise errors.SmartProtocolError("bad response %r" % (resp,))
1011
class SmartTCPTransport(SmartTransport):
1012
"""Connection to smart server over plain tcp"""
1014
def __init__(self, url, clone_from=None):
1015
super(SmartTCPTransport, self).__init__(url, clone_from)
460
except (ValueError, TypeError), e:
461
raise errors.InvalidURL(
462
path=url, extra="invalid port %s" % _port)
463
client_medium = medium.SmartTCPClientMedium(_host, _port)
464
super(RemoteTCPTransport, self).__init__(url, medium=client_medium)
467
class RemoteSSHTransport(RemoteTransport):
468
"""Connection to smart server over SSH.
470
This is essentially just a factory to get 'RemoteTransport(url,
471
SmartSSHClientMedium).
474
def __init__(self, url):
475
_scheme, _username, _password, _host, _port, _path = \
476
transport.split_url(url)
1017
self._port = int(self._port)
478
if _port is not None:
1018
480
except (ValueError, TypeError), e:
1019
raise errors.InvalidURL(path=url, extra="invalid port %s" % self._port)
1022
def _connect_to_server(self):
1023
self._socket = socket.socket()
1024
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
1025
result = self._socket.connect_ex((self._host, int(self._port)))
1027
raise errors.ConnectionError("failed to connect to %s:%d: %s" %
1028
(self._host, self._port, os.strerror(result)))
1029
# TODO: May be more efficient to just treat them as sockets
1030
# throughout? But what about pipes to ssh?...
1031
to_server = self._socket.makefile('w')
1032
from_server = self._socket.makefile('r')
1033
return from_server, to_server
1035
def disconnect(self):
1036
super(SmartTCPTransport, self).disconnect()
1037
# XXX: Is closing the socket as well as closing the files really
1039
if self._socket is not None:
1040
self._socket.close()
1043
from bzrlib.transport import sftp, ssh
1044
except errors.ParamikoNotPresent:
1045
# no paramiko, no SSHTransport.
1048
class SmartSSHTransport(SmartTransport):
1049
"""Connection to smart server over SSH."""
1051
def __init__(self, url, clone_from=None):
1052
# TODO: all this probably belongs in the parent class.
1053
super(SmartSSHTransport, self).__init__(url, clone_from)
1055
if self._port is not None:
1056
self._port = int(self._port)
1057
except (ValueError, TypeError), e:
1058
raise errors.InvalidURL(path=url, extra="invalid port %s" % self._port)
1060
def _connect_to_server(self):
1061
executable = os.environ.get('BZR_REMOTE_PATH', 'bzr')
1062
vendor = ssh._get_ssh_vendor()
1063
self._ssh_connection = vendor.connect_ssh(self._username,
1064
self._password, self._host, self._port,
1065
command=[executable, 'serve', '--inet', '--directory=/',
1067
return self._ssh_connection.get_filelike_channels()
1069
def disconnect(self):
1070
super(SmartSSHTransport, self).disconnect()
1071
self._ssh_connection.close()
481
raise errors.InvalidURL(path=url, extra="invalid port %s" %
483
client_medium = medium.SmartSSHClientMedium(_host, _port,
484
_username, _password)
485
super(RemoteSSHTransport, self).__init__(url, medium=client_medium)
488
class RemoteHTTPTransport(RemoteTransport):
489
"""Just a way to connect between a bzr+http:// url and http://.
491
This connection operates slightly differently than the RemoteSSHTransport.
492
It uses a plain http:// transport underneath, which defines what remote
493
.bzr/smart URL we are connected to. From there, all paths that are sent are
494
sent as relative paths, this way, the remote side can properly
495
de-reference them, since it is likely doing rewrite rules to translate an
496
HTTP path into a local path.
499
def __init__(self, url, http_transport=None):
500
assert url.startswith('bzr+http://')
502
if http_transport is None:
503
http_url = url[len('bzr+'):]
504
self._http_transport = transport.get_transport(http_url)
506
self._http_transport = http_transport
507
http_medium = self._http_transport.get_smart_medium()
508
super(RemoteHTTPTransport, self).__init__(url, medium=http_medium)
510
def _remote_path(self, relpath):
511
"""After connecting HTTP Transport only deals in relative URLs."""
512
# Adjust the relpath based on which URL this smart transport is
514
base = urlutils.normalize_url(self._http_transport.base)
515
url = urlutils.join(self.base[len('bzr+'):], relpath)
516
url = urlutils.normalize_url(url)
517
return urlutils.relative_url(base, url)
519
def abspath(self, relpath):
520
"""Return the full url to the given relative path.
522
:param relpath: the relative path or path components
523
:type relpath: str or list
525
return self._unparse_url(self._combine_paths(self._path, relpath))
527
def clone(self, relative_url):
528
"""Make a new RemoteHTTPTransport related to me.
530
This is re-implemented rather than using the default
531
RemoteTransport.clone() because we must be careful about the underlying
534
Also, the cloned smart transport will POST to the same .bzr/smart
535
location as this transport (although obviously the relative paths in the
536
smart requests may be different). This is so that the server doesn't
537
have to handle .bzr/smart requests at arbitrary places inside .bzr
538
directories, just at the initial URL the user uses.
540
The exception is parent paths (i.e. relative_url of "..").
543
abs_url = self.abspath(relative_url)
546
# We either use the exact same http_transport (for child locations), or
547
# a clone of the underlying http_transport (for parent locations). This
548
# means we share the connection.
549
norm_base = urlutils.normalize_url(self.base)
550
norm_abs_url = urlutils.normalize_url(abs_url)
551
normalized_rel_url = urlutils.relative_url(norm_base, norm_abs_url)
552
if normalized_rel_url == ".." or normalized_rel_url.startswith("../"):
553
http_transport = self._http_transport.clone(normalized_rel_url)
555
http_transport = self._http_transport
556
return RemoteHTTPTransport(abs_url, http_transport=http_transport)
1074
559
def get_test_permutations():
1075
"""Return (transport, server) permutations for testing"""
1076
return [(SmartTCPTransport, SmartTCPServer_for_testing)]
560
"""Return (transport, server) permutations for testing."""
561
### We may need a little more test framework support to construct an
562
### appropriate RemoteTransport in the future.
563
from bzrlib.smart import server
564
return [(RemoteTCPTransport, server.SmartTCPServer_for_testing)]