Repository navigation
Conversation
waynercheung
left a comment
There was a problem hiding this comment.
Verified the root cause against Jetty 9.4.58: HttpInput can pass the same Content to the interceptor repeatedly (on arrival and on subsequent reads), and LimitInterceptor adds remaining() each time, so unread bytes are counted more than once. getContentReceived() is incremented once per addContent and reset per request, which avoids this.
Local run (JDK 17): SizeLimitHandlerTest 23/23 and JsonrpcServiceTest#testJsonRpcSizeLimitIntegration pass. Mutants for the original handler, super.handle(), setInterceptor, the Content-Length precheck and the streaming check are all killed. A one-byte-read probe reproduces the early rejection with the original handler and confirms the fix.
No runtime correctness issue found. Please address the Javadoc note and the description fix below before merge.
[SHOULD] Please correct the error-handling row in the description: JsonRpcServlet reads the network body before invoking jsonrpc4j, and oversized chunked requests propagate as HTTP 413, as asserted by testJsonRpcSizeLimitIntegration. HTTP 200 with an error body can still occur in HTTP servlets that swallow the exception; it does not describe the current JSON-RPC body-reading path.
add one-byte read regressions for exact-limit acceptance and oversized request rejection, plus concurrent counter isolation and keep-alive reset coverage. rename the request-only handler and document why its parent handle method is bypassed.
|
Addressed the review feedback in
Validation on the code merged with upstream A pre-merge clean build passed. The pre-merge full test run was interrupted to merge upstream and run the selected tests directly; no completed full-suite pass is claimed for the merged head. @0xbigapple @waynercheung, could you please review the updated commit? Both inline threads have detailed replies. |
| } | ||
|
|
||
| /** POSTs with a specific client chunk size and returns the response body. */ | ||
| private String postChunked(URI uri, byte[] data, int chunkSize) throws Exception { |
There was a problem hiding this comment.
[NIT] Some of the new tests can block instead of failing if the server stops responding. postChunked opens an HttpURLConnection without setConnectTimeout / setReadTimeout (both default to unlimited). postForBody goes through the shared client from HttpClients.createDefault() (line 181), which has no socket timeout. sendRaw (line 645), used by the new HTTP/1.0 and malformed-chunk tests, opens a plain Socket without setSoTimeout. None of the new test methods have @Test(timeout = ...). A method-level timeout, or connect/read timeouts on these clients, would turn a hang into a test failure. RequestSizeLimitHandlerTest already avoids this with setSoTimeout(15000) on its sockets.
There was a problem hiding this comment.
@SeriousCoding789 Addressed in 9be048ad1b.
Added 15-second connect/read timeouts to the HttpURLConnection and raw Socket helpers. The shared Apache HTTP client now also has 15-second connect, connection-pool acquisition and socket timeouts.
The 28 focused Jetty/JSON-RPC tests passed. Supplemental local fault probes invoked the actual three test helpers against loopback servers that accepted connections but never responded; all three raised SocketTimeoutException after approximately 15 seconds. These probes validate read timeouts; connection timeout configuration was checked separately.
| } | ||
|
|
||
| @Test | ||
| public void testHttp10ChunkedRequestCompatibility() throws Exception { |
There was a problem hiding this comment.
[NIT] A few assertions pin Jetty or test-servlet behavior that is independent of the counter fix:
testHttp10ChunkedRequestCompatibility(line 521) checks that Jetty acceptsTransfer-Encoding: chunkedon an HTTP/1.0 request, which HTTP/1.0 does not define.testMalformedChunkFailsDuringBodyRead(line 530) checks Jetty's exception text (Early EOF) and the 200 status produced by the test servlet's catch-all.RequestSizeLimitHandlerTestline 207 (assertSame(inputs.get("/reuse0"), inputs.get("/reuse" + i))) checks that keep-alive requests reuse the sameHttpInputinstance.
If a Jetty upgrade changes any of these, the tests would fail even though the size limit still works. Would it make sense to drop the first two, or label them as documenting current behavior? For the keep-alive test, Assume.assumeTrue(...) on the instance reuse would keep it as a precondition of the scenario rather than a requirement, while the per-request received assertions still verify the counter reset.
There was a problem hiding this comment.
@SeriousCoding789 Keeping these assertions unchanged in this bounded follow-up. The HTTP/1.0 chunked and malformed-input tests record the pinned Jetty version's compatibility/error behavior, rather than a protocol guarantee. Removing or restructuring them is not required for the inheritance/timeout changes.
The same-HttpInput assertion confirms that the keep-alive scenario actually exercises counter reset on a reused input. Replacing it with an assumption could skip that regression coverage. We can reassess these expectations when upgrading Jetty.
SeriousCoding789
left a comment
There was a problem hiding this comment.
[DISCUSS] Pre-existing and outside the scope of this PR, mentioning it only because the chunked path is touched here: the status code for an oversized body still depends on framing and endpoint. Oversized Content-Length requests get 413 before dispatch, and JSON-RPC returns 413 for oversized chunked bodies, but plain HTTP servlets catch the BadMessageException raised during the body read and answer 200 with an error JSON (the behavior testChunkedBodyExceedsLimit documents). A client that branches on the status code sees a success status for a rejected request. This was scoped out in #6658, so a separate issue might be a better place for it than this PR.
extend HandlerWrapper directly and preserve request-limit checks bound test client connection and read waits to 15 seconds
|
@SeriousCoding789 Regarding the HTTP 413 discussion: agreed that uniform status handling should be addressed separately; it is outside this PR. Extending Validation on
These are scoped local checks, not production acceptance or a complete repository test-suite pass. Full-repository test attempts were stopped; no full-suite pass is claimed. Uniform HTTP 413 handling, broader configuration/deployment combinations and sustained resource-pressure testing are outside this follow-up. These supplemental probes are local evidence, not additional committed tests. |
waynercheung
left a comment
There was a problem hiding this comment.
Re-reviewed 9be048ad1b. Extending HandlerWrapper preserves the request-limit condition, 413 status and exception message, while making super.handle() safe and retaining its null-handler check. The test timeouts are appropriate.
In my local run on JDK 17, all 28 focused tests passed, and targeted mutations covering counter selection, interceptor preservation, size boundaries and rejection behavior were each caught.
Agree that the existing HTTP 200 versus 413 inconsistency in ordinary HTTP servlets should be tracked separately.
[SHOULD] Please sync the description with 9be048ad1b. The handler now extends HandlerWrapper and delegates safely through super.handle(). Statements about super(requestLimit, -1), bypassing SizeLimitHandler.handle(), direct getHandler().handle() delegation, and responseLimit being fixed to -1 describe the previous implementation; the "future responseLimit support" section also assumes it. Please update those sections while keeping the root-cause explanation and the request-only scope.
Previous code review concerns are addressed. The remaining item is to align the PR description with the current implementation; I will approve once it is updated.
|
@waynercheung Thanks for the re-review. Updated the PR description to match the current implementation: The root-cause explanation, request-only scope and validation/impact matrices are retained; historical implementation notes and repeated explanations have been removed. Uniform HTTP 413 handling remains a separate follow-up. No code changes were needed. |
waynercheung
left a comment
There was a problem hiding this comment.
Thanks for syncing the description. It now matches 9be048ad1b: the handler extends HandlerWrapper, delegates through super.handle(), and the response-limit rows describe the absence of an output limit rather than a fixed -1. The JSON-RPC 413 and request-only scope notes are kept. Code is unchanged since my last review, so my earlier local results still apply.
My remaining review concern is addressed. LGTM.
What does this PR do?
Fix premature rejection of valid unknown-length request bodies in the shared Jetty HTTP service. With a 4 MiB limit, the same 2–3 MiB body can pass with Content-Length but fail when sent chunked.
Install
RequestSizeLimitHandler, which extendsHandlerWrapperdirectly and owns the request-limit check. Preserve the Content-Length precheck; for unknown-length bodies, useHttpInput.getContentReceived()throughaddInterceptor(), preserving existing input interceptors and returning each Content unchanged. Delegate throughsuper.handle(), retaining its null-handler check. Configured limits, thesize > limitboundary and existing public error behavior remain unchanged.Test clients and sockets use 15-second connection/read timeouts, including the Apache client's connection-pool acquisition timeout.
Why are these changes required?
Root cause
In Jetty
9.4.58.v20250814,SizeLimitHandler.LimitInterceptor.readFrom()accumulatescontent.remaining()on every interception. The interceptor contract allows the same Content to pass through repeatedly. Becauseremaining()is a snapshot of unread bytes, this counts bytes more than once and makes rejection depend on buffering, chunk boundaries and application read patterns. The known Content-Length precheck is unaffected.Alternatives and selected design
Util.checkBodySize()ServletInputStream/ReaderCounter timing and request isolation
Jetty updates its received-byte counter when new content arrives, before invoking the input interceptor. Updates and reads use the same input monitor; re-intercepting existing content does not increment the counter. Chunked accounting covers payload bytes, excluding framing and trailers.
Concurrent HTTP/1 connections have separate
HttpInputinstances. Keep-alive connections may reuse an input, but Jetty resets its counters and clears the old interceptor for the next request. The shared handler stores only the immutable limit; each interceptor captures its request's input.This PR has been tested by:
All 28 focused Jetty/JSON-RPC tests passed. One-byte regression tests distinguish the received-byte implementation from both a consumed-byte counter and the original Jetty handler. Additional coverage checks concurrent-request isolation, keep-alive counter reset, null-handler delegation and test-helper read timeouts.
clean build -x test, all three required module Checkstyle tasks and the Math check over tracked Java sources passed. These are scoped validation results; no completed full-repository test-suite pass or sustained overload/recovery result is claimed.Component test matrix
The matrix includes committed regressions and supplemental local probes. Supplemental probes are component-level evidence, not a claim that every production deployment was exercised. Detailed validation evidence.
SizeLimitHandlerTest: 23/23 passed, including existing regressions.GetBlockByNumServletwith a mocked Wallet.JsonrpcServiceTest.testJsonRpcSizeLimitIntegrationpassed through JsonRpcServlet/jsonrpc4j.Follow up
Uniform HTTP 413 handling should be addressed separately. Some ordinary HTTP servlets catch body-read exceptions and return HTTP 200 with an error body; this PR preserves that behavior.
Extra details
Test scope and boundaries
ReadListenerconsumptionsetReadListener/startAsyncregistration was found in the production sources checked. Current regression tests exercise the synchronous Reader/InputStream consumers used here; an asynchronous consumer must bring its own integration tests when introduced.HttpService.initContextHandler()installation path; no subclass override of that method was found. Existing per-service limit selection is unchanged. Shared-handler tests, two-service limit isolation and real HTTP/JSON-RPC integration cover the changed mechanism. Repeating identical byte-count tests on every port is not required to validate that shared implementation; this does not claim every service deployment was exercised.Component impact matrix
RequestSizeLimitHandler/HttpInputsize > limitboundary remain. Direct component and socket coverage listed above.HttpServiceUtil.checkBodySize()remains a separate post-read check. Complete configuration-loading validation is not claimed.