Skip to content

HTTP/2 GOAWAY handling limitations and proposed exception #8

@laeubi

Description

@laeubi

Key Problem

Different GOAWAY error codes require different handling strategies:

// Current: Cannot distinguish between these scenarios
try {
    client.send(request, BodyHandlers.ofString());
} catch (IOException e) {
    // Was it NO_ERROR (safe retry) or ENHANCE_YOUR_CALM (backoff needed)?
    // Was it PROTOCOL_ERROR (client bug) or REFUSED_STREAM (retry OK)?
}

// Proposed: Explicit error code handling
catch (HttpGoAwayException e) {
    switch (e.getErrorCode()) {
        case NO_ERROR: retryImmediately(); break;
        case ENHANCE_YOUR_CALM: exponentialBackoff(); break;
        case PROTOCOL_ERROR: reportBug(); break;
    }
}

Establishes foundation for future HttpGoAwayException implementation, following precedent of HttpTimeoutException and HttpConnectTimeoutException.

See GOAWAY.md for a more complete elaboration and JavaHttpClientGoawayTest for a testcase

@Test
@DisplayName("HTTP/2 GOAWAY can be handled")
public void testHttp2GoawayIsHandled() throws Exception {
logger.info("\n=== Testing HTTP/2 GOAWAY ===");
HttpClient client = httpsClient();
HttpRequest request = HttpRequest.newBuilder().uri(getHttpsUrl()).GET().build();
logger.info("Sending HTTP request to " + getHttpsUrl());
CompletableFuture<HttpResponse<String>> responseAsync = client.sendAsync(request,
HttpResponse.BodyHandlers.ofString());
try {
responseAsync.join();
} catch (Exception e) {
// whatever happens, we just want to make sure the request is complete
logger.info("Caught exception during join: " + e);
}
httpsServer.assertGoaway();
try {
HttpResponse<String> response = responseAsync.get();
logger.info("Response status: {}", response.statusCode());
logger.info("Response version: {}", response.version());
logger.info("Response body: {}", response.body());
logger.info("Response headers: {}", response.headers().map());
assertEquals(200, response.statusCode(), "Expected 200 OK response");
assertNotNull(response.body(), "Response body should not be null");
logger.info("=== HTTP/2 Upgrade test completed successfully ===\n");
} catch (ExecutionException e) {
Throwable cause = e.getCause();
cause.printStackTrace();
// Here we have no way to know any of the details of the GOAWAY see GOAWAY.md
// for further rationale.
assertNotEquals(IOException.class, cause.getClass());
}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions