41
33
self._medium = medium
42
34
if headers is None:
44
b'Software version': breezy.__version__.encode('utf-8')}
35
self._headers = {'Software version': bzrlib.__version__}
46
37
self._headers = dict(headers)
48
39
def __repr__(self):
49
40
return '%s(%r)' % (self.__class__.__name__, self._medium)
42
def _send_request(self, protocol_version, method, args, body=None,
43
readv_body=None, body_stream=None):
44
encoder, response_handler = self._construct_protocol(
46
encoder.set_headers(self._headers)
48
if readv_body is not None:
50
"body and readv_body are mutually exclusive.")
51
if body_stream is not None:
53
"body and body_stream are mutually exclusive.")
54
encoder.call_with_body_bytes((method, ) + args, body)
55
elif readv_body is not None:
56
if body_stream is not None:
58
"readv_body and body_stream are mutually exclusive.")
59
encoder.call_with_body_readv_array((method, ) + args, readv_body)
60
elif body_stream is not None:
61
encoder.call_with_body_stream((method, ) + args, body_stream)
63
encoder.call(method, *args)
64
return response_handler
66
def _run_call_hooks(self, method, args, body, readv_body):
67
if not _SmartClient.hooks['call']:
69
params = CallHookParams(method, args, body, readv_body, self._medium)
70
for hook in _SmartClient.hooks['call']:
51
73
def _call_and_read_response(self, method, args, body=None, readv_body=None,
52
body_stream=None, expect_response_body=True):
53
request = _SmartClientRequest(self, method, args, body=body,
54
readv_body=readv_body, body_stream=body_stream,
55
expect_response_body=expect_response_body)
56
return request.call_and_read_response()
74
body_stream=None, expect_response_body=True):
75
self._run_call_hooks(method, args, body, readv_body)
76
if self._medium._protocol_version is not None:
77
response_handler = self._send_request(
78
self._medium._protocol_version, method, args, body=body,
79
readv_body=readv_body, body_stream=body_stream)
80
return (response_handler.read_response_tuple(
81
expect_body=expect_response_body),
84
for protocol_version in [3, 2]:
85
if protocol_version == 2:
86
# If v3 doesn't work, the remote side is older than 1.6.
87
self._medium._remember_remote_is_before((1, 6))
88
response_handler = self._send_request(
89
protocol_version, method, args, body=body,
90
readv_body=readv_body, body_stream=body_stream)
92
response_tuple = response_handler.read_response_tuple(
93
expect_body=expect_response_body)
94
except errors.UnexpectedProtocolVersionMarker, err:
95
# TODO: We could recover from this without disconnecting if
96
# we recognise the protocol version.
98
'Server does not understand Bazaar network protocol %d,'
99
' reconnecting. (Upgrade the server to avoid this.)'
100
% (protocol_version,))
101
self._medium.disconnect()
103
except errors.ErrorFromSmartServer:
104
# If we received an error reply from the server, then it
105
# must be ok with this protocol version.
106
self._medium._protocol_version = protocol_version
109
self._medium._protocol_version = protocol_version
110
return response_tuple, response_handler
111
raise errors.SmartProtocolError(
112
'Server is not a Bazaar server: ' + str(err))
114
def _construct_protocol(self, version):
115
request = self._medium.get_request()
117
request_encoder = protocol.ProtocolThreeRequester(request)
118
response_handler = message.ConventionalResponseHandler()
119
response_proto = protocol.ProtocolThreeDecoder(
120
response_handler, expect_version_marker=True)
121
response_handler.setProtoAndMediumRequest(response_proto, request)
123
request_encoder = protocol.SmartClientRequestProtocolTwo(request)
124
response_handler = request_encoder
126
request_encoder = protocol.SmartClientRequestProtocolOne(request)
127
response_handler = request_encoder
128
return request_encoder, response_handler
58
130
def call(self, method, *args):
59
131
"""Call a method on the remote server."""
116
188
anything but path, so it is only safe to use it in requests sent over
117
189
the medium from the matching transport.
119
return self._medium.remote_path_from_transport(transport).encode('utf-8')
122
class _SmartClientRequest(object):
123
"""Encapsulate the logic for a single request.
125
This class handles things like reconnecting and sending the request a
126
second time when the connection is reset in the middle. It also handles the
127
multiple requests that get made if we don't know what protocol the server
130
Generally, you build up one of these objects, passing in the arguments that
131
you want to send to the server, and then use 'call_and_read_response' to
132
get the response from the server.
135
def __init__(self, client, method, args, body=None, readv_body=None,
136
body_stream=None, expect_response_body=True):
141
self.readv_body = readv_body
142
self.body_stream = body_stream
143
self.expect_response_body = expect_response_body
145
def call_and_read_response(self):
146
"""Send the request to the server, and read the initial response.
148
This doesn't read all of the body content of the response, instead it
149
returns (response_tuple, response_handler). response_tuple is the 'ok',
150
or 'error' information, and 'response_handler' can be used to get the
153
self._run_call_hooks()
154
protocol_version = self.client._medium._protocol_version
155
if protocol_version is None:
156
return self._call_determining_protocol_version()
158
return self._call(protocol_version)
160
def _is_safe_to_send_twice(self):
161
"""Check if the current method is re-entrant safe."""
162
if self.body_stream is not None or 'noretry' in debug.debug_flags:
163
# We can't restart a body stream that has already been consumed.
165
request_type = _mod_request.request_handlers.get_info(self.method)
166
if request_type in ('read', 'idem', 'semi'):
168
# If we have gotten this far, 'stream' cannot be retried, because we
169
# already consumed the local stream.
170
if request_type in ('semivfs', 'mutate', 'stream'):
172
trace.mutter('Unknown request type: %s for method %s'
173
% (request_type, self.method))
176
def _run_call_hooks(self):
177
if not _SmartClient.hooks['call']:
179
params = CallHookParams(self.method, self.args, self.body,
180
self.readv_body, self.client._medium)
181
for hook in _SmartClient.hooks['call']:
184
def _call(self, protocol_version):
185
"""We know the protocol version.
187
So this just sends the request, and then reads the response. This is
188
where the code will be to retry requests if the connection is closed.
190
response_handler = self._send(protocol_version)
192
response_tuple = response_handler.read_response_tuple(
193
expect_body=self.expect_response_body)
194
except errors.ConnectionReset as e:
195
self.client._medium.reset()
196
if not self._is_safe_to_send_twice():
198
trace.warning('ConnectionReset reading response for %r, retrying'
200
trace.log_exception_quietly()
201
encoder, response_handler = self._construct_protocol(
203
self._send_no_retry(encoder)
204
response_tuple = response_handler.read_response_tuple(
205
expect_body=self.expect_response_body)
206
return (response_tuple, response_handler)
208
def _call_determining_protocol_version(self):
209
"""Determine what protocol the remote server supports.
211
We do this by placing a request in the most recent protocol, and
212
handling the UnexpectedProtocolVersionMarker from the server.
215
for protocol_version in [3, 2]:
216
if protocol_version == 2:
217
# If v3 doesn't work, the remote side is older than 1.6.
218
self.client._medium._remember_remote_is_before((1, 6))
220
response_tuple, response_handler = self._call(protocol_version)
221
except errors.UnexpectedProtocolVersionMarker as err:
222
# TODO: We could recover from this without disconnecting if
223
# we recognise the protocol version.
225
'Server does not understand Bazaar network protocol %d,'
226
' reconnecting. (Upgrade the server to avoid this.)'
227
% (protocol_version,))
228
self.client._medium.disconnect()
231
except errors.ErrorFromSmartServer:
232
# If we received an error reply from the server, then it
233
# must be ok with this protocol version.
234
self.client._medium._protocol_version = protocol_version
237
self.client._medium._protocol_version = protocol_version
238
return response_tuple, response_handler
239
raise errors.SmartProtocolError(
240
'Server is not a Bazaar server: ' + str(last_err))
242
def _construct_protocol(self, version):
243
"""Build the encoding stack for a given protocol version."""
244
request = self.client._medium.get_request()
246
request_encoder = protocol.ProtocolThreeRequester(request)
247
response_handler = message.ConventionalResponseHandler()
248
response_proto = protocol.ProtocolThreeDecoder(
249
response_handler, expect_version_marker=True)
250
response_handler.setProtoAndMediumRequest(response_proto, request)
252
request_encoder = protocol.SmartClientRequestProtocolTwo(request)
253
response_handler = request_encoder
255
request_encoder = protocol.SmartClientRequestProtocolOne(request)
256
response_handler = request_encoder
257
return request_encoder, response_handler
259
def _send(self, protocol_version):
260
"""Encode the request, and send it to the server.
262
This will retry a request if we get a ConnectionReset while sending the
263
request to the server. (Unless we have a body_stream that we have
264
already started consuming, since we can't restart body_streams)
266
:return: response_handler as defined by _construct_protocol
268
encoder, response_handler = self._construct_protocol(protocol_version)
270
self._send_no_retry(encoder)
271
except errors.ConnectionReset as e:
272
# If we fail during the _send_no_retry phase, then we can
273
# be confident that the server did not get our request, because we
274
# haven't started waiting for the reply yet. So try the request
275
# again. We only issue a single retry, because if the connection
276
# really is down, there is no reason to loop endlessly.
278
# Connection is dead, so close our end of it.
279
self.client._medium.reset()
280
if (('noretry' in debug.debug_flags) or
281
(self.body_stream is not None and
282
encoder.body_stream_started)):
283
# We can't restart a body_stream that has been partially
284
# consumed, so we don't retry.
285
# Note: We don't have to worry about
286
# SmartClientRequestProtocolOne or Two, because they don't
287
# support client-side body streams.
289
trace.warning('ConnectionReset calling %r, retrying'
291
trace.log_exception_quietly()
292
encoder, response_handler = self._construct_protocol(
294
self._send_no_retry(encoder)
295
return response_handler
297
def _send_no_retry(self, encoder):
298
"""Just encode the request and try to send it."""
299
encoder.set_headers(self.client._headers)
300
if self.body is not None:
301
if self.readv_body is not None:
302
raise AssertionError(
303
"body and readv_body are mutually exclusive.")
304
if self.body_stream is not None:
305
raise AssertionError(
306
"body and body_stream are mutually exclusive.")
307
encoder.call_with_body_bytes(
308
(self.method, ) + self.args, self.body)
309
elif self.readv_body is not None:
310
if self.body_stream is not None:
311
raise AssertionError(
312
"readv_body and body_stream are mutually exclusive.")
313
encoder.call_with_body_readv_array((self.method, ) + self.args,
315
elif self.body_stream is not None:
316
encoder.call_with_body_stream((self.method, ) + self.args,
319
encoder.call(self.method, *self.args)
191
return self._medium.remote_path_from_transport(transport)
322
194
class SmartClientHooks(hooks.Hooks):
324
196
def __init__(self):
325
hooks.Hooks.__init__(
326
self, "breezy.bzr.smart.client", "_SmartClient.hooks")
327
self.add_hook('call',
328
"Called when the smart client is submitting a request to the "
329
"smart server. Called with a breezy.bzr.smart.client.CallHookParams "
330
"object. Streaming request bodies, and responses, are not "
197
hooks.Hooks.__init__(self)
198
self.create_hook(hooks.HookPoint('call',
199
"Called when the smart client is submitting a request to the "
200
"smart server. Called with a bzrlib.smart.client.CallHookParams "
201
"object. Streaming request bodies, and responses, are not "
202
"accessible.", None, None))
334
205
_SmartClient.hooks = SmartClientHooks()