/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/remote.py

  • Committer: Andrew Bennetts
  • Date: 2007-04-06 01:19:30 UTC
  • mto: This revision was merged to the branch mainline in revision 2402.
  • Revision ID: andrew.bennetts@canonical.com-20070406011930-wy4emdgu3yu527pm
Move SmartTCPServer classes into bzrlib/smart/server.py

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
 
"""Smart-server protocol, client and server.
18
 
 
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. ::
22
 
 
23
 
  SEP := '\001'
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
33
 
  ERROR_TRAILER := 
34
 
 
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.
38
 
 
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.)
45
 
 
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?
49
 
 
50
 
At the bottom level socket, pipes, HTTP server.  For sockets, we have the idea
51
 
that you have multiple requests and get a read error because the other side did
52
 
shutdown.  For pipes we have read pipe which will have a zero read which marks
53
 
end-of-file.  For HTTP server environment there is not end-of-stream because
54
 
each request coming into the server is independent.
55
 
 
56
 
So we need a wrapper around pipes and sockets to seperate out requests from
57
 
substrate and this will give us a single model which is consist for HTTP,
58
 
sockets and pipes.
59
 
 
60
 
Server-side
61
 
-----------
62
 
 
63
 
 MEDIUM  (factory for protocol, reads bytes & pushes to protocol,
64
 
          uses protocol to detect end-of-request, sends written
65
 
          bytes to client) e.g. socket, pipe, HTTP request handler.
66
 
  ^
67
 
  | bytes.
68
 
  v
69
 
 
70
 
PROTOCOL  (serialization, deserialization)  accepts bytes for one
71
 
          request, decodes according to internal state, pushes
72
 
          structured data to handler.  accepts structured data from
73
 
          handler and encodes and writes to the medium.  factory for
74
 
          handler.
75
 
  ^
76
 
  | structured data
77
 
  v
78
 
 
79
 
HANDLER   (domain logic) accepts structured data, operates state
80
 
          machine until the request can be satisfied,
81
 
          sends structured data to the protocol.
82
 
 
83
 
 
84
 
Client-side
85
 
-----------
86
 
 
87
 
 CLIENT             domain logic, accepts domain requests, generated structured
88
 
                    data, reads structured data from responses and turns into
89
 
                    domain data.  Sends structured data to the protocol.
90
 
                    Operates state machines until the request can be delivered
91
 
                    (e.g. reading from a bundle generated in bzrlib to deliver a
92
 
                    complete request).
93
 
 
94
 
                    Possibly this should just be RemoteBzrDir, RemoteTransport,
95
 
                    ...
96
 
  ^
97
 
  | structured data
98
 
  v
99
 
 
100
 
PROTOCOL  (serialization, deserialization)  accepts structured data for one
101
 
          request, encodes and writes to the medium.  Reads bytes from the
102
 
          medium, decodes and allows the client to read structured data.
103
 
  ^
104
 
  | bytes.
105
 
  v
106
 
 
107
 
 MEDIUM  (accepts bytes from the protocol & delivers to the remote server.
108
 
          Allows the potocol to read bytes e.g. socket, pipe, HTTP request.
109
 
"""
110
 
 
111
 
 
112
 
# TODO: _translate_error should be on the client, not the transport because
113
 
#     error coding is wire protocol specific.
114
 
 
115
 
# TODO: A plain integer from query_version is too simple; should give some
116
 
# capabilities too?
117
 
 
118
 
# TODO: Server should probably catch exceptions within itself and send them
119
 
# back across the network.  (But shouldn't catch KeyboardInterrupt etc)
120
 
# Also needs to somehow report protocol errors like bad requests.  Need to
121
 
# consider how we'll handle error reporting, e.g. if we get halfway through a
122
 
# bulk transfer and then something goes wrong.
123
 
 
124
 
# TODO: Standard marker at start of request/response lines?
125
 
 
126
 
# TODO: Make each request and response self-validatable, e.g. with checksums.
127
 
#
128
 
# TODO: get/put objects could be changed to gradually read back the data as it
129
 
# comes across the network
130
 
#
131
 
# TODO: What should the server do if it hits an error and has to terminate?
132
 
#
133
 
# TODO: is it useful to allow multiple chunks in the bulk data?
134
 
#
135
 
# TODO: If we get an exception during transmission of bulk data we can't just
136
 
# emit the exception because it won't be seen.
137
 
#   John proposes:  I think it would be worthwhile to have a header on each
138
 
#   chunk, that indicates it is another chunk. Then you can send an 'error'
139
 
#   chunk as long as you finish the previous chunk.
140
 
#
141
 
# TODO: Clone method on Transport; should work up towards parent directory;
142
 
# unclear how this should be stored or communicated to the server... maybe
143
 
# just pass it on all relevant requests?
144
 
#
145
 
# TODO: Better name than clone() for changing between directories.  How about
146
 
# open_dir or change_dir or chdir?
147
 
#
148
 
# TODO: Is it really good to have the notion of current directory within the
149
 
# connection?  Perhaps all Transports should factor out a common connection
150
 
# from the thing that has the directory context?
151
 
#
152
 
# TODO: Pull more things common to sftp and ssh to a higher level.
153
 
#
154
 
# TODO: The server that manages a connection should be quite small and retain
155
 
# minimum state because each of the requests are supposed to be stateless.
156
 
# Then we can write another implementation that maps to http.
157
 
#
158
 
# TODO: What to do when a client connection is garbage collected?  Maybe just
159
 
# abruptly drop the connection?
160
 
#
161
 
# TODO: Server in some cases will need to restrict access to files outside of
162
 
# a particular root directory.  LocalTransport doesn't do anything to stop you
163
 
# ascending above the base directory, so we need to prevent paths
164
 
# containing '..' in either the server or transport layers.  (Also need to
165
 
# consider what happens if someone creates a symlink pointing outside the 
166
 
# directory tree...)
167
 
#
168
 
# TODO: Server should rebase absolute paths coming across the network to put
169
 
# them under the virtual root, if one is in use.  LocalTransport currently
170
 
# doesn't do that; if you give it an absolute path it just uses it.
171
 
172
 
# XXX: Arguments can't contain newlines or ascii; possibly we should e.g.
173
 
# urlescape them instead.  Indeed possibly this should just literally be
174
 
# http-over-ssh.
175
 
#
176
 
# FIXME: This transport, with several others, has imperfect handling of paths
177
 
# within urls.  It'd probably be better for ".." from a root to raise an error
178
 
# rather than return the same directory as we do at present.
179
 
#
180
 
# TODO: Rather than working at the Transport layer we want a Branch,
181
 
# Repository or BzrDir objects that talk to a server.
182
 
#
183
 
# TODO: Probably want some way for server commands to gradually produce body
184
 
# data rather than passing it as a string; they could perhaps pass an
185
 
# iterator-like callback that will gradually yield data; it probably needs a
186
 
# close() method that will always be closed to do any necessary cleanup.
187
 
#
188
 
# TODO: Split the actual smart server from the ssh encoding of it.
189
 
#
190
 
# TODO: Perhaps support file-level readwrite operations over the transport
191
 
# too.
192
 
#
193
 
# TODO: SmartBzrDir class, proxying all Branch etc methods across to another
194
 
# branch doing file-level operations.
195
 
#
196
 
 
197
17
from cStringIO import StringIO
198
18
import os
199
19
import socket
819
639
                raise
820
640
 
821
641
 
822
 
class SmartTCPServer(object):
823
 
    """Listens on a TCP socket and accepts connections from smart clients"""
824
 
 
825
 
    def __init__(self, backing_transport, host='127.0.0.1', port=0):
826
 
        """Construct a new server.
827
 
 
828
 
        To actually start it running, call either start_background_thread or
829
 
        serve.
830
 
 
831
 
        :param host: Name of the interface to listen on.
832
 
        :param port: TCP port to listen on, or 0 to allocate a transient port.
833
 
        """
834
 
        self._server_socket = socket.socket()
835
 
        self._server_socket.bind((host, port))
836
 
        self.port = self._server_socket.getsockname()[1]
837
 
        self._server_socket.listen(1)
838
 
        self._server_socket.settimeout(1)
839
 
        self.backing_transport = backing_transport
840
 
 
841
 
    def serve(self):
842
 
        # let connections timeout so that we get a chance to terminate
843
 
        # Keep a reference to the exceptions we want to catch because the socket
844
 
        # module's globals get set to None during interpreter shutdown.
845
 
        from socket import timeout as socket_timeout
846
 
        from socket import error as socket_error
847
 
        self._should_terminate = False
848
 
        while not self._should_terminate:
849
 
            try:
850
 
                self.accept_and_serve()
851
 
            except socket_timeout:
852
 
                # just check if we're asked to stop
853
 
                pass
854
 
            except socket_error, e:
855
 
                trace.warning("client disconnected: %s", e)
856
 
                pass
857
 
 
858
 
    def get_url(self):
859
 
        """Return the url of the server"""
860
 
        return "bzr://%s:%d/" % self._server_socket.getsockname()
861
 
 
862
 
    def accept_and_serve(self):
863
 
        conn, client_addr = self._server_socket.accept()
864
 
        # For WIN32, where the timeout value from the listening socket
865
 
        # propogates to the newly accepted socket.
866
 
        conn.setblocking(True)
867
 
        conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
868
 
        handler = SmartServerSocketStreamMedium(conn, self.backing_transport)
869
 
        connection_thread = threading.Thread(None, handler.serve, name='smart-server-child')
870
 
        connection_thread.setDaemon(True)
871
 
        connection_thread.start()
872
 
 
873
 
    def start_background_thread(self):
874
 
        self._server_thread = threading.Thread(None,
875
 
                self.serve,
876
 
                name='server-' + self.get_url())
877
 
        self._server_thread.setDaemon(True)
878
 
        self._server_thread.start()
879
 
 
880
 
    def stop_background_thread(self):
881
 
        self._should_terminate = True
882
 
        # At one point we would wait to join the threads here, but it looks
883
 
        # like they don't actually exit.  So now we just leave them running
884
 
        # and expect to terminate the process. -- mbp 20070215
885
 
        # self._server_socket.close()
886
 
        ## sys.stderr.write("waiting for server thread to finish...")
887
 
        ## self._server_thread.join()
888
 
 
889
 
 
890
 
class SmartTCPServer_for_testing(SmartTCPServer):
891
 
    """Server suitable for use by transport tests.
892
 
    
893
 
    This server is backed by the process's cwd.
894
 
    """
895
 
 
896
 
    def __init__(self):
897
 
        self._homedir = urlutils.local_path_to_url(os.getcwd())[7:]
898
 
        # The server is set up by default like for ssh access: the client
899
 
        # passes filesystem-absolute paths; therefore the server must look
900
 
        # them up relative to the root directory.  it might be better to act
901
 
        # a public server and have the server rewrite paths into the test
902
 
        # directory.
903
 
        SmartTCPServer.__init__(self,
904
 
            transport.get_transport(urlutils.local_path_to_url('/')))
905
 
        
906
 
    def get_backing_transport(self, backing_transport_server):
907
 
        """Get a backing transport from a server we are decorating."""
908
 
        return transport.get_transport(backing_transport_server.get_url())
909
 
 
910
 
    def setUp(self, backing_transport_server=None):
911
 
        """Set up server for testing"""
912
 
        from bzrlib.transport.chroot import TestingChrootServer
913
 
        if backing_transport_server is None:
914
 
            from bzrlib.transport.local import LocalURLServer
915
 
            backing_transport_server = LocalURLServer()
916
 
        self.chroot_server = TestingChrootServer()
917
 
        self.chroot_server.setUp(backing_transport_server)
918
 
        self.backing_transport = transport.get_transport(
919
 
            self.chroot_server.get_url())
920
 
        self.start_background_thread()
921
 
 
922
 
    def tearDown(self):
923
 
        self.stop_background_thread()
924
 
 
925
 
    def get_bogus_url(self):
926
 
        """Return a URL which will fail to connect"""
927
 
        return 'bzr://127.0.0.1:1/'
928
 
 
929
 
 
930
642
class SmartStat(object):
931
643
 
932
644
    def __init__(self, size, mode):
1838
1550
 
1839
1551
def get_test_permutations():
1840
1552
    """Return (transport, server) permutations for testing."""
 
1553
    from bzrlib.smart import server
1841
1554
    ### We may need a little more test framework support to construct an
1842
1555
    ### appropriate RemoteTransport in the future.
1843
 
    return [(SmartTCPTransport, SmartTCPServer_for_testing)]
 
1556
    return [(SmartTCPTransport, server.SmartTCPServer_for_testing)]