Show More
@@ -513,7 +513,9 b' def sendv2request(ui, opener, requestbui' | |||||
513 | reactor = wireprotoframing.clientreactor(hasmultiplesend=False, |
|
513 | reactor = wireprotoframing.clientreactor(hasmultiplesend=False, | |
514 | buffersends=True) |
|
514 | buffersends=True) | |
515 |
|
515 | |||
516 |
handler = wireprotov2peer.clienthandler(ui, reactor |
|
516 | handler = wireprotov2peer.clienthandler(ui, reactor, | |
|
517 | opener=opener, | |||
|
518 | requestbuilder=requestbuilder) | |||
517 |
|
519 | |||
518 | url = '%s/%s' % (apiurl, permission) |
|
520 | url = '%s/%s' % (apiurl, permission) | |
519 |
|
521 |
@@ -13,8 +13,12 b' from .i18n import _' | |||||
13 | from . import ( |
|
13 | from . import ( | |
14 | encoding, |
|
14 | encoding, | |
15 | error, |
|
15 | error, | |
|
16 | pycompat, | |||
16 | sslutil, |
|
17 | sslutil, | |
|
18 | url as urlmod, | |||
|
19 | util, | |||
17 | wireprotoframing, |
|
20 | wireprotoframing, | |
|
21 | wireprototypes, | |||
18 | ) |
|
22 | ) | |
19 | from .utils import ( |
|
23 | from .utils import ( | |
20 | cborutil, |
|
24 | cborutil, | |
@@ -112,9 +116,10 b' class commandresponse(object):' | |||||
112 | events occur. |
|
116 | events occur. | |
113 | """ |
|
117 | """ | |
114 |
|
118 | |||
115 | def __init__(self, requestid, command): |
|
119 | def __init__(self, requestid, command, fromredirect=False): | |
116 | self.requestid = requestid |
|
120 | self.requestid = requestid | |
117 | self.command = command |
|
121 | self.command = command | |
|
122 | self.fromredirect = fromredirect | |||
118 |
|
123 | |||
119 | # Whether all remote input related to this command has been |
|
124 | # Whether all remote input related to this command has been | |
120 | # received. |
|
125 | # received. | |
@@ -132,6 +137,7 b' class commandresponse(object):' | |||||
132 | self._pendingevents = [] |
|
137 | self._pendingevents = [] | |
133 | self._decoder = cborutil.bufferingdecoder() |
|
138 | self._decoder = cborutil.bufferingdecoder() | |
134 | self._seeninitial = False |
|
139 | self._seeninitial = False | |
|
140 | self._redirect = None | |||
135 |
|
141 | |||
136 | def _oninputcomplete(self): |
|
142 | def _oninputcomplete(self): | |
137 | with self._lock: |
|
143 | with self._lock: | |
@@ -146,10 +152,19 b' class commandresponse(object):' | |||||
146 |
|
152 | |||
147 | with self._lock: |
|
153 | with self._lock: | |
148 | for o in self._decoder.getavailable(): |
|
154 | for o in self._decoder.getavailable(): | |
149 | if not self._seeninitial: |
|
155 | if not self._seeninitial and not self.fromredirect: | |
150 | self._handleinitial(o) |
|
156 | self._handleinitial(o) | |
151 | continue |
|
157 | continue | |
152 |
|
158 | |||
|
159 | # We should never see an object after a content redirect, | |||
|
160 | # as the spec says the main status object containing the | |||
|
161 | # content redirect is the only object in the stream. Fail | |||
|
162 | # if we see a misbehaving server. | |||
|
163 | if self._redirect: | |||
|
164 | raise error.Abort(_('received unexpected response data ' | |||
|
165 | 'after content redirect; the remote is ' | |||
|
166 | 'buggy')) | |||
|
167 | ||||
153 | self._pendingevents.append(o) |
|
168 | self._pendingevents.append(o) | |
154 |
|
169 | |||
155 | self._serviceable.set() |
|
170 | self._serviceable.set() | |
@@ -160,7 +175,16 b' class commandresponse(object):' | |||||
160 | return |
|
175 | return | |
161 |
|
176 | |||
162 | elif o[b'status'] == b'redirect': |
|
177 | elif o[b'status'] == b'redirect': | |
163 | raise error.Abort(_('redirect responses not yet supported')) |
|
178 | l = o[b'location'] | |
|
179 | self._redirect = wireprototypes.alternatelocationresponse( | |||
|
180 | url=l[b'url'], | |||
|
181 | mediatype=l[b'mediatype'], | |||
|
182 | size=l.get(b'size'), | |||
|
183 | fullhashes=l.get(b'fullhashes'), | |||
|
184 | fullhashseed=l.get(b'fullhashseed'), | |||
|
185 | serverdercerts=l.get(b'serverdercerts'), | |||
|
186 | servercadercerts=l.get(b'servercadercerts')) | |||
|
187 | return | |||
164 |
|
188 | |||
165 | atoms = [{'msg': o[b'error'][b'message']}] |
|
189 | atoms = [{'msg': o[b'error'][b'message']}] | |
166 | if b'args' in o[b'error']: |
|
190 | if b'args' in o[b'error']: | |
@@ -214,13 +238,17 b' class clienthandler(object):' | |||||
214 | with the higher-level peer API. |
|
238 | with the higher-level peer API. | |
215 | """ |
|
239 | """ | |
216 |
|
240 | |||
217 |
def __init__(self, ui, clientreactor |
|
241 | def __init__(self, ui, clientreactor, opener=None, | |
|
242 | requestbuilder=util.urlreq.request): | |||
218 | self._ui = ui |
|
243 | self._ui = ui | |
219 | self._reactor = clientreactor |
|
244 | self._reactor = clientreactor | |
220 | self._requests = {} |
|
245 | self._requests = {} | |
221 | self._futures = {} |
|
246 | self._futures = {} | |
222 | self._responses = {} |
|
247 | self._responses = {} | |
|
248 | self._redirects = [] | |||
223 | self._frameseof = False |
|
249 | self._frameseof = False | |
|
250 | self._opener = opener or urlmod.opener(ui) | |||
|
251 | self._requestbuilder = requestbuilder | |||
224 |
|
252 | |||
225 | def callcommand(self, command, args, f, redirect=None): |
|
253 | def callcommand(self, command, args, f, redirect=None): | |
226 | """Register a request to call a command. |
|
254 | """Register a request to call a command. | |
@@ -269,7 +297,12 b' class clienthandler(object):' | |||||
269 | self._ui.note(_('received %r\n') % frame) |
|
297 | self._ui.note(_('received %r\n') % frame) | |
270 | self._processframe(frame) |
|
298 | self._processframe(frame) | |
271 |
|
299 | |||
272 | if self._frameseof: |
|
300 | # Also try to read the first redirect. | |
|
301 | if self._redirects: | |||
|
302 | if not self._processredirect(*self._redirects[0]): | |||
|
303 | self._redirects.pop(0) | |||
|
304 | ||||
|
305 | if self._frameseof and not self._redirects: | |||
273 | return None |
|
306 | return None | |
274 |
|
307 | |||
275 | return True |
|
308 | return True | |
@@ -318,10 +351,27 b' class clienthandler(object):' | |||||
318 | # This can raise. The caller can handle it. |
|
351 | # This can raise. The caller can handle it. | |
319 | response._onresponsedata(meta['data']) |
|
352 | response._onresponsedata(meta['data']) | |
320 |
|
353 | |||
|
354 | # If we got a content redirect response, we want to fetch it and | |||
|
355 | # expose the data as if we received it inline. But we also want to | |||
|
356 | # keep our internal request accounting in order. Our strategy is to | |||
|
357 | # basically put meaningful response handling on pause until EOS occurs | |||
|
358 | # and the stream accounting is in a good state. At that point, we follow | |||
|
359 | # the redirect and replace the response object with its data. | |||
|
360 | ||||
|
361 | redirect = response._redirect | |||
|
362 | handlefuture = False if redirect else True | |||
|
363 | ||||
321 | if meta['eos']: |
|
364 | if meta['eos']: | |
322 | response._oninputcomplete() |
|
365 | response._oninputcomplete() | |
323 | del self._requests[frame.requestid] |
|
366 | del self._requests[frame.requestid] | |
324 |
|
367 | |||
|
368 | if redirect: | |||
|
369 | self._followredirect(frame.requestid, redirect) | |||
|
370 | return | |||
|
371 | ||||
|
372 | if not handlefuture: | |||
|
373 | return | |||
|
374 | ||||
325 | # If the command has a decoder, we wait until all input has been |
|
375 | # If the command has a decoder, we wait until all input has been | |
326 | # received before resolving the future. Otherwise we resolve the |
|
376 | # received before resolving the future. Otherwise we resolve the | |
327 | # future immediately. |
|
377 | # future immediately. | |
@@ -336,6 +386,82 b' class clienthandler(object):' | |||||
336 | self._futures[frame.requestid].set_result(decoded) |
|
386 | self._futures[frame.requestid].set_result(decoded) | |
337 | del self._futures[frame.requestid] |
|
387 | del self._futures[frame.requestid] | |
338 |
|
388 | |||
|
389 | def _followredirect(self, requestid, redirect): | |||
|
390 | """Called to initiate redirect following for a request.""" | |||
|
391 | self._ui.note(_('(following redirect to %s)\n') % redirect.url) | |||
|
392 | ||||
|
393 | # TODO handle framed responses. | |||
|
394 | if redirect.mediatype != b'application/mercurial-cbor': | |||
|
395 | raise error.Abort(_('cannot handle redirects for the %s media type') | |||
|
396 | % redirect.mediatype) | |||
|
397 | ||||
|
398 | if redirect.fullhashes: | |||
|
399 | self._ui.warn(_('(support for validating hashes on content ' | |||
|
400 | 'redirects not supported)\n')) | |||
|
401 | ||||
|
402 | if redirect.serverdercerts or redirect.servercadercerts: | |||
|
403 | self._ui.warn(_('(support for pinning server certificates on ' | |||
|
404 | 'content redirects not supported)\n')) | |||
|
405 | ||||
|
406 | headers = { | |||
|
407 | r'Accept': redirect.mediatype, | |||
|
408 | } | |||
|
409 | ||||
|
410 | req = self._requestbuilder(pycompat.strurl(redirect.url), None, headers) | |||
|
411 | ||||
|
412 | try: | |||
|
413 | res = self._opener.open(req) | |||
|
414 | except util.urlerr.httperror as e: | |||
|
415 | if e.code == 401: | |||
|
416 | raise error.Abort(_('authorization failed')) | |||
|
417 | raise | |||
|
418 | except util.httplib.HTTPException as e: | |||
|
419 | self._ui.debug('http error requesting %s\n' % req.get_full_url()) | |||
|
420 | self._ui.traceback() | |||
|
421 | raise IOError(None, e) | |||
|
422 | ||||
|
423 | urlmod.wrapresponse(res) | |||
|
424 | ||||
|
425 | # The existing response object is associated with frame data. Rather | |||
|
426 | # than try to normalize its state, just create a new object. | |||
|
427 | oldresponse = self._responses[requestid] | |||
|
428 | self._responses[requestid] = commandresponse(requestid, | |||
|
429 | oldresponse.command, | |||
|
430 | fromredirect=True) | |||
|
431 | ||||
|
432 | self._redirects.append((requestid, res)) | |||
|
433 | ||||
|
434 | def _processredirect(self, rid, res): | |||
|
435 | """Called to continue processing a response from a redirect.""" | |||
|
436 | response = self._responses[rid] | |||
|
437 | ||||
|
438 | try: | |||
|
439 | data = res.read(32768) | |||
|
440 | response._onresponsedata(data) | |||
|
441 | ||||
|
442 | # We're at end of stream. | |||
|
443 | if not data: | |||
|
444 | response._oninputcomplete() | |||
|
445 | ||||
|
446 | if rid not in self._futures: | |||
|
447 | return | |||
|
448 | ||||
|
449 | if response.command not in COMMAND_DECODERS: | |||
|
450 | self._futures[rid].set_result(response.objects()) | |||
|
451 | del self._futures[rid] | |||
|
452 | elif response._inputcomplete: | |||
|
453 | decoded = COMMAND_DECODERS[response.command](response.objects()) | |||
|
454 | self._futures[rid].set_result(decoded) | |||
|
455 | del self._futures[rid] | |||
|
456 | ||||
|
457 | return bool(data) | |||
|
458 | ||||
|
459 | except BaseException as e: | |||
|
460 | self._futures[rid].set_exception(e) | |||
|
461 | del self._futures[rid] | |||
|
462 | response._oninputcomplete() | |||
|
463 | return False | |||
|
464 | ||||
339 | def decodebranchmap(objs): |
|
465 | def decodebranchmap(objs): | |
340 | # Response should be a single CBOR map of branch name to array of nodes. |
|
466 | # Response should be a single CBOR map of branch name to array of nodes. | |
341 | bm = next(objs) |
|
467 | bm = next(objs) |
@@ -1354,8 +1354,33 b' 2nd request should result in content red' | |||||
1354 | s> 0\r\n |
|
1354 | s> 0\r\n | |
1355 | s> \r\n |
|
1355 | s> \r\n | |
1356 | received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) |
|
1356 | received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) | |
1357 | abort: redirect responses not yet supported |
|
1357 | (following redirect to http://*:$HGPORT/api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0) (glob) | |
1358 | [255] |
|
1358 | s> GET /api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0 HTTP/1.1\r\n | |
|
1359 | s> Accept-Encoding: identity\r\n | |||
|
1360 | s> accept: application/mercurial-cbor\r\n | |||
|
1361 | s> host: *:$HGPORT\r\n (glob) | |||
|
1362 | s> user-agent: Mercurial debugwireproto\r\n | |||
|
1363 | s> \r\n | |||
|
1364 | s> makefile('rb', None) | |||
|
1365 | s> HTTP/1.1 200 OK\r\n | |||
|
1366 | s> Server: testing stub value\r\n | |||
|
1367 | s> Date: $HTTP_DATE$\r\n | |||
|
1368 | s> Content-Type: application/mercurial-cbor\r\n | |||
|
1369 | s> Content-Length: 91\r\n | |||
|
1370 | s> \r\n | |||
|
1371 | s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 | |||
|
1372 | response: gen[ | |||
|
1373 | { | |||
|
1374 | b'totalitems': 1 | |||
|
1375 | }, | |||
|
1376 | { | |||
|
1377 | b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', | |||
|
1378 | b'parents': [ | |||
|
1379 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', | |||
|
1380 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | |||
|
1381 | ] | |||
|
1382 | } | |||
|
1383 | ] | |||
1359 |
|
1384 | |||
1360 | $ cat error.log |
|
1385 | $ cat error.log | |
1361 | $ killdaemons.py |
|
1386 | $ killdaemons.py |
General Comments 0
You need to be logged in to leave comments.
Login now