Skip to content

Send the request body again when a redirection keeps the request method - #6348

Open
jnbdz wants to merge 1 commit into
eclipse-vertx:masterfrom
SiteNetSoft:redirect-replay-body
Open

Send the request body again when a redirection keeps the request method#6348
jnbdz wants to merge 1 commit into
eclipse-vertx:masterfrom
SiteNetSoft:redirect-replay-body

Conversation

@jnbdz

@jnbdz jnbdz commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Motivation

Since #6212 the body of a QUERY request is buffered (within ClientRedirectConfig#setMaxBufferedSize) and sent again when the request is redirected. The body of any other request is still dropped: a custom redirect handler that follows a 307 or 308 redirection of a POST or PUT request, as RFC 9110 requires for those status codes, ends up sending the redirected request with an empty body. This is what quarkusio/quarkus#41751 reports for the Quarkus REST Client, and @vietj said there that an extension of the redirect handling in Vert.x 5 would be welcome if there was demand for it.

Changes

  • the body of any request whose method can carry one (everything but GET, HEAD and CONNECT) is buffered while followRedirects is set, within the existing max buffered size, and sent again whenever the redirected request keeps the method. This is the QUERY mechanism generalised, nothing else changes for QUERY;
  • when the body exceeds the limit, redirections other than 303 are not followed, as already documented for QUERY, since they may keep the method and need the body. A 303 still turns the request into a bodyless GET and is followed;
  • the redirected request now buffers its own body instead of sharing the buffer list of the previous request: sharing it appended the replayed body to the same list, so a request redirected twice sent the body twice;
  • the default redirect handler policy is unchanged: 307 and 308 redirections of methods other than GET, HEAD and QUERY are still only followed by a custom handler. Javadoc and the http.adoc redirection section are updated accordingly.

Tests

Five tests in HttpTest use a redirect handler that keeps the method on 307/308: POST on 307 and PUT on 308 with a body, a chunked POST written in two buffers, a body over the limit on 307 (the 307 is returned to the caller) and a body over the limit on 303 (followed as GET). The first four fail on master, all pass with this change, and the existing testFollowRedirect* tests as well as Http1xTest, Http2Test and Http1xTLSTest (1024 tests) pass on both HTTP versions.

The body of a QUERY request is buffered and sent again when the request
is redirected, but the body of any other request is dropped: a custom
redirect handler following a 307 or 308 redirection of a POST or PUT
request sends the redirected request without its body, while RFC 9110
requires those redirections to keep the method and the body.

Buffer the body of any request whose method can carry one, within the
existing max buffered size, and send it again whenever the redirected
request keeps the method. When the body exceeds the limit, redirections
other than 303 are not followed, as documented for QUERY, since they may
need the body. The redirected request now buffers its own body instead of
sharing the list of the previous one, which appended the body a second
time when the request was redirected again.

The default redirect handler policy is unchanged: 307 and 308 redirections
of methods other than GET, HEAD and QUERY are still only followed by a
custom redirect handler.
@vietj

vietj commented Sep 7, 2026

Copy link
Copy Markdown
Member

@jnbdz are you using an LLM to assist your contributions ?

/**
* @return whether the body of a request using {@code method} is sent again when a redirection keeps the method
*/
private static boolean canRedirectBody(HttpMethod method) {

@vietj vietj Sep 7, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this should be the reverse, we should only redirect bodies when the method is QUERY or POST or PUT

/**
* A client following 307 and 308 redirections with the same method, like an HTTP client compliant with RFC 9110 would do.
*/
private HttpClientAgent keepMethodRedirectClient() {

@vietj vietj Sep 7, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should be named preservingMethodRedirectClient

.await();
}

private void testFollowRedirectWithBodyKeepingMethod(HttpMethod method, int statusCode, int bodySize) throws Exception {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Preserving not Keeping

client.request(opts).compose(req -> {
req.setFollowRedirects(true);
req.setChunked(true);
req.write(chunk1);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the first write and end should be composed futures

handler.tryFail(new VertxException("Cannot follow the " + getMethod() + " redirection: the request body " +
"exceeds the maximum size that can be buffered for redirections (" + maxRedirectBufferSize + " bytes)", true));
next.reset(0);
return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am not fan of this return, we can avoid it ?

for (Buffer b : bodyBuffer) {
composite.addComponent(true, ((BufferInternal) b).getByteBuf());
if (getMethod().equals(next.getMethod()) && canRedirectBody(getMethod())) {
// The redirection keeps the method, so the body is sent again

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

"preserves" the method, not "keeps"

@@ -63,6 +63,7 @@ public class HttpClientRequestImpl extends HttpClientRequestBase implements Http
private String traceOperation;
private int maxRedirectBufferSize = HttpClientOptions.DEFAULT_MAX_REDIRECT_BUFFERED_SIZE;
private List<Buffer> bodyBuffer;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

that should be renamed "bufferedBody" instead

bodyBuffer.add(buff.copy());
}
if (currentLen + buff.length() > maxRedirectBufferSize) {
bodyBufferDiscarded = true;

@vietj vietj Sep 7, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why do we need to introduce bodyBufferDiscarded insted of using followRedirects ? it is not clear, adding another variable increases the complexity

@vietj

vietj commented Sep 7, 2026

Copy link
Copy Markdown
Member

if we only add support for new methods, I don't get why the implementation of HttpClientRequest needs so many changes, can you avoid changing the implementation to keep changes to the strict minimum ?

@vietj

vietj commented Sep 7, 2026

Copy link
Copy Markdown
Member

I also believe that this should be configurable and we should think more about the security implications of such redirections. QUERY introduces a clear semantic of redirecting a payload, for other HTTP methods it is not clearly defined, specially regarding cross origin redirection.

So until we have clearly defined and analyzed the security implications of this change, I am not in favor to implement and make this available out of the box.

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.

2 participants