|
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;
|
@@ -410,10 +411,62 @@ private CompletableFuture<Response> sendRequestBody(ExchangeImpl<T> ex) {
|
410 | 411 | CompletableFuture<Response> cf = ex.sendBodyAsync()
|
411 | 412 | .thenCompose(exIm -> exIm.getResponseAsync(parentExecutor));
|
412 | 413 | cf = wrapForUpgrade(cf);
|
| 414 | + // after 101 is handled we check for other 1xx responses |
| 415 | + cf = cf.thenCompose(this::ignore1xxResponse); |
413 | 416 | cf = wrapForLog(cf);
|
414 | 417 | return cf;
|
415 | 418 | }
|
416 | 419 |
|
| 420 | + /** |
| 421 | + * Checks whether the passed Response has a status code between 102 and 199 (both inclusive). |
| 422 | + * If so, then that {@code Response} is considered intermediate informational response and is |
| 423 | + * ignored by the client. This method then creates a new {@link CompletableFuture} which |
| 424 | + * completes when a subsequent response is sent by the server. Such newly constructed |
| 425 | + * {@link CompletableFuture} will not complete till a "final" response (one which doesn't have |
| 426 | + * a response code between 102 and 199 inclusive) is sent by the server. The returned |
| 427 | + * {@link CompletableFuture} is thus capable of handling multiple subsequent intermediate |
| 428 | + * informational responses from the server. |
| 429 | + * <p> |
| 430 | + * If the passed Response doesn't have a status code between 102 and 199 (both inclusive) then |
| 431 | + * this method immediately returns back a completed {@link CompletableFuture} with the passed |
| 432 | + * {@code Response}. |
| 433 | + * </p> |
| 434 | + * |
| 435 | + * @param rsp The response |
| 436 | + * @return A {@code CompletableFuture} with the final response from the server |
| 437 | + */ |
| 438 | + private CompletableFuture<Response> ignore1xxResponse(final Response rsp) { |
| 439 | + final int statusCode = rsp.statusCode(); |
| 440 | + // we ignore any response code which is 1xx. |
| 441 | + // For 100 (with the request configured to expect-continue) and 101, we handle it |
| 442 | + // specifically as defined in the RFC-9110, outside of this method. |
| 443 | + // As noted in RFC-9110, section 15.2.1, if response code is 100 and if the request wasn't |
| 444 | + // configured with expectContinue, then we ignore the 100 response and wait for the final |
| 445 | + // response (just like any other 1xx response). |
| 446 | + // Any other response code between 102 and 199 (both inclusive) aren't specified in the |
| 447 | + // "HTTP semantics" RFC-9110. The spec states that these 1xx response codes are informational |
| 448 | + // and interim and the client can choose to ignore them and continue to wait for the |
| 449 | + // final response (headers) |
| 450 | + if ((statusCode >= 102 && statusCode <= 199) |
| 451 | + || (statusCode == 100 && !request.expectContinue)) { |
| 452 | + Log.logTrace("Ignoring (1xx informational) response code {0}", rsp.statusCode()); |
| 453 | + if (debug.on()) { |
| 454 | + debug.log("Ignoring (1xx informational) response code " |
| 455 | + + rsp.statusCode()); |
| 456 | + } |
| 457 | + assert exchImpl != null : "Illegal state - current exchange isn't set"; |
| 458 | + // ignore this Response and wait again for the subsequent response headers |
| 459 | + final CompletableFuture<Response> cf = exchImpl.getResponseAsync(parentExecutor); |
| 460 | + // we recompose the CF again into the ignore1xxResponse check/function because |
| 461 | + // the 1xx response is allowed to be sent multiple times for a request, before |
| 462 | + // a final response arrives |
| 463 | + return cf.thenCompose(this::ignore1xxResponse); |
| 464 | + } else { |
| 465 | + // return the already completed future |
| 466 | + return MinimalFuture.completedFuture(rsp); |
| 467 | + } |
| 468 | + } |
| 469 | + |
417 | 470 | CompletableFuture<Response> responseAsyncImpl0(HttpConnection connection) {
|
418 | 471 | Function<ExchangeImpl<T>, CompletableFuture<Response>> after407Check;
|
419 | 472 | bodyIgnored = null;
|
@@ -444,7 +497,30 @@ private CompletableFuture<Response> wrapForUpgrade(CompletableFuture<Response> c
|
444 | 497 | if (upgrading) {
|
445 | 498 | return cf.thenCompose(r -> checkForUpgradeAsync(r, exchImpl));
|
446 | 499 | }
|
447 |
| - return cf; |
| 500 | + // websocket requests use "Connection: Upgrade" and "Upgrade: websocket" headers. |
| 501 | + // however, the "upgrading" flag we maintain in this class only tracks a h2 upgrade |
| 502 | + // that we internally triggered. So it will be false in the case of websocket upgrade, hence |
| 503 | + // this additional check. If it's a websocket request we allow 101 responses and we don't |
| 504 | + // require any additional checks when a response arrives. |
| 505 | + if (request.isWebSocket()) { |
| 506 | + return cf; |
| 507 | + } |
| 508 | + // not expecting an upgrade, but if the server sends a 101 response then we fail the |
| 509 | + // request and also let the ExchangeImpl deal with it as a protocol error |
| 510 | + return cf.thenCompose(r -> { |
| 511 | + if (r.statusCode == 101) { |
| 512 | + final ProtocolException protoEx = new ProtocolException("Unexpected 101 " + |
| 513 | + "response, when not upgrading"); |
| 514 | + assert exchImpl != null : "Illegal state - current exchange isn't set"; |
| 515 | + try { |
| 516 | + exchImpl.onProtocolError(protoEx); |
| 517 | + } catch (Throwable ignore){ |
| 518 | + // ignored |
| 519 | + } |
| 520 | + return MinimalFuture.failedFuture(protoEx); |
| 521 | + } |
| 522 | + return MinimalFuture.completedFuture(r); |
| 523 | + }); |
448 | 524 | }
|
449 | 525 |
|
450 | 526 | private CompletableFuture<Response> wrapForLog(CompletableFuture<Response> cf) {
|
|
0 commit comments