Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use HTTP status code 413 for exceedingly large payloads #631

Merged
merged 1 commit into from
Jan 9, 2025

Conversation

dimas-b
Copy link
Contributor

@dimas-b dimas-b commented Jan 8, 2025

The 413 status code seems to be more appropriate than
400 (Bad Request) in this case.

@@ -722,7 +722,7 @@ public void testRequestBodyTooLarge() {
.header(REALM_PROPERTY_KEY, realm)
.post(largeRequest)) {
assertThat(response)
.returns(Response.Status.BAD_REQUEST.getStatusCode(), Response::getStatus)
.returns(Response.Status.REQUEST_ENTITY_TOO_LARGE.getStatusCode(), Response::getStatus)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO we do need to take connection closed into account. With Quarkus for sure, maybe even with Dropwizard. From my recollection and manual testing, both frameworks close the connection on this response error. The problem is that sometimes the client receives the response payload before the connection is closed, sometimes after. Again from my recollection, in Quarkus, 1 execution out of 3 or 4 ends up with connection closed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From RFC 9110 on status 413:

The server MAY terminate the request, if the protocol version in use allows it; otherwise, the server MAY close the connection.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RFC 9110 defines the protocol-level behaviour, but in Polaris, the REST endpoint produces a response object with a status code in this case. I would not think Quarkus closing the connection prematurely would be correct. I believe Quarkus (or any other Web App framework) ought to send the response object to the client.

@adutra : are you sure the connection resets happened in the "body" test and and not in the "header" test? The headers are handled by the Web Apop framework, so the connection reset decision lies with the framework itself. Still, the error on large payload objects is handled differently.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you sure the connection resets happened in the "body" test and and not in the "header" test?

Yes 😄 – those two tests gave me enough headaches that I still remember...

The headers are handled by the Web Apop framework,

In Quarkus, both cases are handled by Vertx.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on the client side the connection can be closed only after the status line is received

Where is that specified? I find it hard to enforce such a guarantee. If the server closes the connection, I don't think there is a way for the TCP layer to close the connection gracefully only after some TCP message containing the status is received by the client.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RFC 9110 Section 3.4 A server responds to a client's request by sending one or more response messages, each including a status code [...]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but from RFC 9112:

If a server performs an immediate close of a TCP connection, there is a significant risk that the client will not be able to read the last HTTP response. If the server receives additional data from the client on a fully closed connection, such as another request sent by the client before receiving the server's response, the server's TCP stack will send a reset packet to the client; unfortunately, the reset packet might erase the client's unacknowledged input buffers before they can be read and interpreted by the client's HTTP parser.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What an interesting :) mess, TIL... In this case, I'd say we should drop both the body and header size tests, because it's way beyond the scope of Polaris to ensure deterministic behaviours of these tests.

I personally do not see much value in a test that either validates the status code or does not validate anything because a connection close exception is inconclusive and does not guarantee that even the request was sent.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... the more it goes the more I think it makes sense to drop both tests.

@dimas-b
Copy link
Contributor Author

dimas-b commented Jan 9, 2025

Rebased and re-implemented per discussion above.

@adutra
Copy link
Contributor

adutra commented Jan 9, 2025

Sorry... I re-checked the Quarkus version that I made a while ago, and I believe it will fail with your current code. The version I had already checks for the status only (no check on the response body). And yet, it fails quite frequently with "connection closed" errors.

The current Quarkus version for reference:

public void testRequestBodyTooLarge() {
Entity<PrincipalRole> largeRequest =
Entity.json(new PrincipalRole("r".repeat((int) (maxBodySize + 1))));
try (Client client = ClientBuilder.newClient()) {
try (Response response =
client
.target(String.format("%s/api/management/v1/principal-roles", testEnv.baseUri()))
.request("application/json")
.header("Authorization", "Bearer " + fixture.adminToken)
.header(REALM_PROPERTY_KEY, fixture.realm)
.post(largeRequest)) {
assertThat(response)
.returns(Response.Status.REQUEST_ENTITY_TOO_LARGE.getStatusCode(), Response::getStatus);
} catch (ProcessingException e) {
// In some runtime environments the request above will return a 431 but in others it'll
// result
// in a ProcessingException from the socket being closed. The test asserts that one of those
// things happens.
assertThat(e).hasMessageContaining("Connection was closed");
}
}
}

The 413 status code seems to be more appropriate than
400 (Bad Request) in this case.
@dimas-b dimas-b force-pushed the message-size-unit-tests branch from 1787adb to 8dc1ee2 Compare January 9, 2025 18:41
@dimas-b
Copy link
Contributor Author

dimas-b commented Jan 9, 2025

For the sake of preventing spurious CI failures, I added a catch block for expected connection closures.

@dimas-b dimas-b requested review from adutra and eric-maynard January 9, 2025 18:42
Comment on lines +714 to +715
// This test assumes that the server under test is configured to deny requests larger than
// 1000000 bytes. The test payload below assumes UTF8 encoding of ASCII charts plus a bit of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to do this here, but I wonder if we could somehow add a check for the limit to this test.

Comment on lines +733 to +737
// Per RFC 9110 servers MAY close the connection in case of 413 responses, which
// might cause the client to fail to read the status code (cf. RFC 9112, section 9.6).
// TODO: servers are expected to close connections gracefully. It might be worth investigating
// whether "connection closed" exceptions are a client-side bug.
assertThat(e).hasMessageContaining("Connection was closed");
Copy link
Contributor

@eric-maynard eric-maynard Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This catch makes sense to me. It might be worth filing an issue to see if we can reproduce the disconnect elsewhere

@dimas-b dimas-b merged commit 551ce87 into apache:main Jan 9, 2025
5 checks passed
@dimas-b dimas-b deleted the message-size-unit-tests branch January 9, 2025 23:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants