|
27 | 27 |
|
28 | 28 | import java.io.IOException;
|
29 | 29 | import java.net.InetSocketAddress;
|
| 30 | +import java.net.ProtocolException; |
30 | 31 | import java.net.ProxySelector;
|
31 | 32 | import java.net.URI;
|
32 | 33 | import java.net.URISyntaxException;
|
@@ -472,10 +473,62 @@ private CompletableFuture<Response> sendRequestBody(ExchangeImpl<T> ex) {
|
472 | 473 | CompletableFuture<Response> cf = ex.sendBodyAsync()
|
473 | 474 | .thenCompose(exIm -> exIm.getResponseAsync(parentExecutor));
|
474 | 475 | cf = wrapForUpgrade(cf);
|
| 476 | + // after 101 is handled we check for other 1xx responses |
| 477 | + cf = cf.thenCompose(this::ignore1xxResponse); |
475 | 478 | cf = wrapForLog(cf);
|
476 | 479 | return cf;
|
477 | 480 | }
|
478 | 481 |
|
| 482 | + /** |
| 483 | + * Checks whether the passed Response has a status code between 102 and 199 (both inclusive). |
| 484 | + * If so, then that {@code Response} is considered intermediate informational response and is |
| 485 | + * ignored by the client. This method then creates a new {@link CompletableFuture} which |
| 486 | + * completes when a subsequent response is sent by the server. Such newly constructed |
| 487 | + * {@link CompletableFuture} will not complete till a "final" response (one which doesn't have |
| 488 | + * a response code between 102 and 199 inclusive) is sent by the server. The returned |
| 489 | + * {@link CompletableFuture} is thus capable of handling multiple subsequent intermediate |
| 490 | + * informational responses from the server. |
| 491 | + * <p> |
| 492 | + * If the passed Response doesn't have a status code between 102 and 199 (both inclusive) then |
| 493 | + * this method immediately returns back a completed {@link CompletableFuture} with the passed |
| 494 | + * {@code Response}. |
| 495 | + * </p> |
| 496 | + * |
| 497 | + * @param rsp The response |
| 498 | + * @return A {@code CompletableFuture} with the final response from the server |
| 499 | + */ |
| 500 | + private CompletableFuture<Response> ignore1xxResponse(final Response rsp) { |
| 501 | + final int statusCode = rsp.statusCode(); |
| 502 | + // we ignore any response code which is 1xx. |
| 503 | + // For 100 (with the request configured to expect-continue) and 101, we handle it |
| 504 | + // specifically as defined in the RFC-9110, outside of this method. |
| 505 | + // As noted in RFC-9110, section 15.2.1, if response code is 100 and if the request wasn't |
| 506 | + // configured with expectContinue, then we ignore the 100 response and wait for the final |
| 507 | + // response (just like any other 1xx response). |
| 508 | + // Any other response code between 102 and 199 (both inclusive) aren't specified in the |
| 509 | + // "HTTP semantics" RFC-9110. The spec states that these 1xx response codes are informational |
| 510 | + // and interim and the client can choose to ignore them and continue to wait for the |
| 511 | + // final response (headers) |
| 512 | + if ((statusCode >= 102 && statusCode <= 199) |
| 513 | + || (statusCode == 100 && !request.expectContinue)) { |
| 514 | + Log.logTrace("Ignoring (1xx informational) response code {0}", rsp.statusCode()); |
| 515 | + if (debug.on()) { |
| 516 | + debug.log("Ignoring (1xx informational) response code " |
| 517 | + + rsp.statusCode()); |
| 518 | + } |
| 519 | + assert exchImpl != null : "Illegal state - current exchange isn't set"; |
| 520 | + // ignore this Response and wait again for the subsequent response headers |
| 521 | + final CompletableFuture<Response> cf = exchImpl.getResponseAsync(parentExecutor); |
| 522 | + // we recompose the CF again into the ignore1xxResponse check/function because |
| 523 | + // the 1xx response is allowed to be sent multiple times for a request, before |
| 524 | + // a final response arrives |
| 525 | + return cf.thenCompose(this::ignore1xxResponse); |
| 526 | + } else { |
| 527 | + // return the already completed future |
| 528 | + return MinimalFuture.completedFuture(rsp); |
| 529 | + } |
| 530 | + } |
| 531 | + |
479 | 532 | CompletableFuture<Response> responseAsyncImpl0(HttpConnection connection) {
|
480 | 533 | Function<ExchangeImpl<T>, CompletableFuture<Response>> after407Check;
|
481 | 534 | bodyIgnored = null;
|
@@ -506,7 +559,30 @@ private CompletableFuture<Response> wrapForUpgrade(CompletableFuture<Response> c
|
506 | 559 | if (upgrading) {
|
507 | 560 | return cf.thenCompose(r -> checkForUpgradeAsync(r, exchImpl));
|
508 | 561 | }
|
509 |
| - return cf; |
| 562 | + // websocket requests use "Connection: Upgrade" and "Upgrade: websocket" headers. |
| 563 | + // however, the "upgrading" flag we maintain in this class only tracks a h2 upgrade |
| 564 | + // that we internally triggered. So it will be false in the case of websocket upgrade, hence |
| 565 | + // this additional check. If it's a websocket request we allow 101 responses and we don't |
| 566 | + // require any additional checks when a response arrives. |
| 567 | + if (request.isWebSocket()) { |
| 568 | + return cf; |
| 569 | + } |
| 570 | + // not expecting an upgrade, but if the server sends a 101 response then we fail the |
| 571 | + // request and also let the ExchangeImpl deal with it as a protocol error |
| 572 | + return cf.thenCompose(r -> { |
| 573 | + if (r.statusCode == 101) { |
| 574 | + final ProtocolException protoEx = new ProtocolException("Unexpected 101 " + |
| 575 | + "response, when not upgrading"); |
| 576 | + assert exchImpl != null : "Illegal state - current exchange isn't set"; |
| 577 | + try { |
| 578 | + exchImpl.onProtocolError(protoEx); |
| 579 | + } catch (Throwable ignore){ |
| 580 | + // ignored |
| 581 | + } |
| 582 | + return MinimalFuture.failedFuture(protoEx); |
| 583 | + } |
| 584 | + return MinimalFuture.completedFuture(r); |
| 585 | + }); |
510 | 586 | }
|
511 | 587 |
|
512 | 588 | private CompletableFuture<Response> wrapForLog(CompletableFuture<Response> cf) {
|
|
0 commit comments