|
28 | 28 | import java.io.IOException;
|
29 | 29 | import java.lang.System.Logger.Level;
|
30 | 30 | import java.net.InetSocketAddress;
|
| 31 | +import java.net.ProtocolException; |
31 | 32 | import java.net.ProxySelector;
|
32 | 33 | import java.net.URI;
|
33 | 34 | import java.net.URISyntaxException;
|
@@ -447,10 +448,62 @@ private CompletableFuture<Response> sendRequestBody(ExchangeImpl<T> ex) {
|
447 | 448 | CompletableFuture<Response> cf = ex.sendBodyAsync()
|
448 | 449 | .thenCompose(exIm -> exIm.getResponseAsync(parentExecutor));
|
449 | 450 | cf = wrapForUpgrade(cf);
|
| 451 | + // after 101 is handled we check for other 1xx responses |
| 452 | + cf = cf.thenCompose(this::ignore1xxResponse); |
450 | 453 | cf = wrapForLog(cf);
|
451 | 454 | return cf;
|
452 | 455 | }
|
453 | 456 |
|
| 457 | + /** |
| 458 | + * Checks whether the passed Response has a status code between 102 and 199 (both inclusive). |
| 459 | + * If so, then that {@code Response} is considered intermediate informational response and is |
| 460 | + * ignored by the client. This method then creates a new {@link CompletableFuture} which |
| 461 | + * completes when a subsequent response is sent by the server. Such newly constructed |
| 462 | + * {@link CompletableFuture} will not complete till a "final" response (one which doesn't have |
| 463 | + * a response code between 102 and 199 inclusive) is sent by the server. The returned |
| 464 | + * {@link CompletableFuture} is thus capable of handling multiple subsequent intermediate |
| 465 | + * informational responses from the server. |
| 466 | + * <p> |
| 467 | + * If the passed Response doesn't have a status code between 102 and 199 (both inclusive) then |
| 468 | + * this method immediately returns back a completed {@link CompletableFuture} with the passed |
| 469 | + * {@code Response}. |
| 470 | + * </p> |
| 471 | + * |
| 472 | + * @param rsp The response |
| 473 | + * @return A {@code CompletableFuture} with the final response from the server |
| 474 | + */ |
| 475 | + private CompletableFuture<Response> ignore1xxResponse(final Response rsp) { |
| 476 | + final int statusCode = rsp.statusCode(); |
| 477 | + // we ignore any response code which is 1xx. |
| 478 | + // For 100 (with the request configured to expect-continue) and 101, we handle it |
| 479 | + // specifically as defined in the RFC-9110, outside of this method. |
| 480 | + // As noted in RFC-9110, section 15.2.1, if response code is 100 and if the request wasn't |
| 481 | + // configured with expectContinue, then we ignore the 100 response and wait for the final |
| 482 | + // response (just like any other 1xx response). |
| 483 | + // Any other response code between 102 and 199 (both inclusive) aren't specified in the |
| 484 | + // "HTTP semantics" RFC-9110. The spec states that these 1xx response codes are informational |
| 485 | + // and interim and the client can choose to ignore them and continue to wait for the |
| 486 | + // final response (headers) |
| 487 | + if ((statusCode >= 102 && statusCode <= 199) |
| 488 | + || (statusCode == 100 && !request.expectContinue)) { |
| 489 | + Log.logTrace("Ignoring (1xx informational) response code {0}", rsp.statusCode()); |
| 490 | + if (debug.on()) { |
| 491 | + debug.log("Ignoring (1xx informational) response code " |
| 492 | + + rsp.statusCode()); |
| 493 | + } |
| 494 | + assert exchImpl != null : "Illegal state - current exchange isn't set"; |
| 495 | + // ignore this Response and wait again for the subsequent response headers |
| 496 | + final CompletableFuture<Response> cf = exchImpl.getResponseAsync(parentExecutor); |
| 497 | + // we recompose the CF again into the ignore1xxResponse check/function because |
| 498 | + // the 1xx response is allowed to be sent multiple times for a request, before |
| 499 | + // a final response arrives |
| 500 | + return cf.thenCompose(this::ignore1xxResponse); |
| 501 | + } else { |
| 502 | + // return the already completed future |
| 503 | + return MinimalFuture.completedFuture(rsp); |
| 504 | + } |
| 505 | + } |
| 506 | + |
454 | 507 | CompletableFuture<Response> responseAsyncImpl0(HttpConnection connection) {
|
455 | 508 | Function<ExchangeImpl<T>, CompletableFuture<Response>> after407Check;
|
456 | 509 | bodyIgnored = null;
|
@@ -481,7 +534,30 @@ private CompletableFuture<Response> wrapForUpgrade(CompletableFuture<Response> c
|
481 | 534 | if (upgrading) {
|
482 | 535 | return cf.thenCompose(r -> checkForUpgradeAsync(r, exchImpl));
|
483 | 536 | }
|
484 |
| - return cf; |
| 537 | + // websocket requests use "Connection: Upgrade" and "Upgrade: websocket" headers. |
| 538 | + // however, the "upgrading" flag we maintain in this class only tracks a h2 upgrade |
| 539 | + // that we internally triggered. So it will be false in the case of websocket upgrade, hence |
| 540 | + // this additional check. If it's a websocket request we allow 101 responses and we don't |
| 541 | + // require any additional checks when a response arrives. |
| 542 | + if (request.isWebSocket()) { |
| 543 | + return cf; |
| 544 | + } |
| 545 | + // not expecting an upgrade, but if the server sends a 101 response then we fail the |
| 546 | + // request and also let the ExchangeImpl deal with it as a protocol error |
| 547 | + return cf.thenCompose(r -> { |
| 548 | + if (r.statusCode == 101) { |
| 549 | + final ProtocolException protoEx = new ProtocolException("Unexpected 101 " + |
| 550 | + "response, when not upgrading"); |
| 551 | + assert exchImpl != null : "Illegal state - current exchange isn't set"; |
| 552 | + try { |
| 553 | + exchImpl.onProtocolError(protoEx); |
| 554 | + } catch (Throwable ignore){ |
| 555 | + // ignored |
| 556 | + } |
| 557 | + return MinimalFuture.failedFuture(protoEx); |
| 558 | + } |
| 559 | + return MinimalFuture.completedFuture(r); |
| 560 | + }); |
485 | 561 | }
|
486 | 562 |
|
487 | 563 | private CompletableFuture<Response> wrapForLog(CompletableFuture<Response> cf) {
|
|
0 commit comments