Show More
@@ -513,7 +513,9 b' def sendv2request(ui, opener, requestbui' | |||
|
513 | 513 | reactor = wireprotoframing.clientreactor(hasmultiplesend=False, |
|
514 | 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 | 520 | url = '%s/%s' % (apiurl, permission) |
|
519 | 521 |
@@ -13,8 +13,12 b' from .i18n import _' | |||
|
13 | 13 | from . import ( |
|
14 | 14 | encoding, |
|
15 | 15 | error, |
|
16 | pycompat, | |
|
16 | 17 | sslutil, |
|
18 | url as urlmod, | |
|
19 | util, | |
|
17 | 20 | wireprotoframing, |
|
21 | wireprototypes, | |
|
18 | 22 | ) |
|
19 | 23 | from .utils import ( |
|
20 | 24 | cborutil, |
@@ -112,9 +116,10 b' class commandresponse(object):' | |||
|
112 | 116 | events occur. |
|
113 | 117 | """ |
|
114 | 118 | |
|
115 | def __init__(self, requestid, command): | |
|
119 | def __init__(self, requestid, command, fromredirect=False): | |
|
116 | 120 | self.requestid = requestid |
|
117 | 121 | self.command = command |
|
122 | self.fromredirect = fromredirect | |
|
118 | 123 | |
|
119 | 124 | # Whether all remote input related to this command has been |
|
120 | 125 | # received. |
@@ -132,6 +137,7 b' class commandresponse(object):' | |||
|
132 | 137 | self._pendingevents = [] |
|
133 | 138 | self._decoder = cborutil.bufferingdecoder() |
|
134 | 139 | self._seeninitial = False |
|
140 | self._redirect = None | |
|
135 | 141 | |
|
136 | 142 | def _oninputcomplete(self): |
|
137 | 143 | with self._lock: |
@@ -146,10 +152,19 b' class commandresponse(object):' | |||
|
146 | 152 | |
|
147 | 153 | with self._lock: |
|
148 | 154 | for o in self._decoder.getavailable(): |
|
149 | if not self._seeninitial: | |
|
155 | if not self._seeninitial and not self.fromredirect: | |
|
150 | 156 | self._handleinitial(o) |
|
151 | 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 | 168 | self._pendingevents.append(o) |
|
154 | 169 | |
|
155 | 170 | self._serviceable.set() |
@@ -160,7 +175,16 b' class commandresponse(object):' | |||
|
160 | 175 | return |
|
161 | 176 | |
|
162 | 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 | 189 | atoms = [{'msg': o[b'error'][b'message']}] |
|
166 | 190 | if b'args' in o[b'error']: |
@@ -214,13 +238,17 b' class clienthandler(object):' | |||
|
214 | 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 | 243 | self._ui = ui |
|
219 | 244 | self._reactor = clientreactor |
|
220 | 245 | self._requests = {} |
|
221 | 246 | self._futures = {} |
|
222 | 247 | self._responses = {} |
|
248 | self._redirects = [] | |
|
223 | 249 | self._frameseof = False |
|
250 | self._opener = opener or urlmod.opener(ui) | |
|
251 | self._requestbuilder = requestbuilder | |
|
224 | 252 | |
|
225 | 253 | def callcommand(self, command, args, f, redirect=None): |
|
226 | 254 | """Register a request to call a command. |
@@ -269,7 +297,12 b' class clienthandler(object):' | |||
|
269 | 297 | self._ui.note(_('received %r\n') % frame) |
|
270 | 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 | 306 | return None |
|
274 | 307 | |
|
275 | 308 | return True |
@@ -318,10 +351,27 b' class clienthandler(object):' | |||
|
318 | 351 | # This can raise. The caller can handle it. |
|
319 | 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 | 364 | if meta['eos']: |
|
322 | 365 | response._oninputcomplete() |
|
323 | 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 | 375 | # If the command has a decoder, we wait until all input has been |
|
326 | 376 | # received before resolving the future. Otherwise we resolve the |
|
327 | 377 | # future immediately. |
@@ -336,6 +386,82 b' class clienthandler(object):' | |||
|
336 | 386 | self._futures[frame.requestid].set_result(decoded) |
|
337 | 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 | 465 | def decodebranchmap(objs): |
|
340 | 466 | # Response should be a single CBOR map of branch name to array of nodes. |
|
341 | 467 | bm = next(objs) |
@@ -1354,8 +1354,33 b' 2nd request should result in content red' | |||
|
1354 | 1354 | s> 0\r\n |
|
1355 | 1355 | s> \r\n |
|
1356 | 1356 | received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) |
|
1357 | abort: redirect responses not yet supported | |
|
1358 | [255] | |
|
1357 | (following redirect to http://*:$HGPORT/api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0) (glob) | |
|
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 | 1385 | $ cat error.log |
|
1361 | 1386 | $ killdaemons.py |
General Comments 0
You need to be logged in to leave comments.
Login now