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