From 173c5814ec0570a7ac59e79e23d33166bf4a1827 Mon Sep 17 00:00:00 2001 From: Ben Thompson Date: Fri, 10 Jul 2026 15:20:36 -0700 Subject: [PATCH] Fix request body re-sent on method-rewritten redirect follows When JMeter core follows a 301/302/303 it rewrites POST to GET, but setBody() gated body attachment on the sampler's configured method instead of the method actually being sent (result.getHTTPMethod()). The follow-up GET therefore carried the original POST entity, unlike HTTPSamplerProxy and RFC 9110 semantics. Skip body attachment when the effective method differs from the sampler's configured method and does not admit a body. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01H3q9uueSAmCEz3P8MS8crD --- .../jmeter/http2/core/HTTP2JettyClient.java | 11 +++++++++- .../http2/core/HTTP2JettyClientTest.java | 22 +++++++++++++++++++ .../jmeter/http2/core/ServerBuilder.java | 6 +++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/blazemeter/jmeter/http2/core/HTTP2JettyClient.java b/src/main/java/com/blazemeter/jmeter/http2/core/HTTP2JettyClient.java index 2648ffca..163cd2d7 100644 --- a/src/main/java/com/blazemeter/jmeter/http2/core/HTTP2JettyClient.java +++ b/src/main/java/com/blazemeter/jmeter/http2/core/HTTP2JettyClient.java @@ -3273,6 +3273,15 @@ private void addProxyIfEmpty(HttpClient target, String host, int port, boolean s private void setBody(Request request, HTTP2Sampler sampler, HTTPSampleResult result) throws IOException { + String effectiveMethod = result.getHTTPMethod(); + if (!effectiveMethod.equals(sampler.getMethod()) && !isMethodWithBody(effectiveMethod)) { + // The method being sent only differs from the sampler's configured one when JMeter core + // rewrites it while following a redirect (e.g. POST -> GET after a 301/302/303). The + // entity belongs to the original request, so the method-rewritten follow-up must not + // re-send it (RFC 9110 section 15.4), matching HTTPSamplerProxy behavior. + result.setQueryString(""); + return; + } String contentEncoding = sampler.getContentEncoding(); String contentTypeHeader = request.getHeaders() != null ? request.getHeaders().get(HTTPConstants.HEADER_CONTENT_TYPE) @@ -3368,7 +3377,7 @@ private void setBody(Request request, HTTP2Sampler sampler, HTTPSampleResult res new StringRequestContent(contentTypeHeader, postBody.toString(), contentCharset); request.body(requestContent); - } else if (isMethodWithBody(sampler.getMethod())) { + } else if (isMethodWithBody(effectiveMethod)) { Fields fields = new Fields(); for (JMeterProperty p : sampler.getArguments()) { HTTPArgument arg = (HTTPArgument) p.getObjectValue(); diff --git a/src/test/java/com/blazemeter/jmeter/http2/core/HTTP2JettyClientTest.java b/src/test/java/com/blazemeter/jmeter/http2/core/HTTP2JettyClientTest.java index 2233d216..ab3304f0 100644 --- a/src/test/java/com/blazemeter/jmeter/http2/core/HTTP2JettyClientTest.java +++ b/src/test/java/com/blazemeter/jmeter/http2/core/HTTP2JettyClientTest.java @@ -19,6 +19,7 @@ import static com.blazemeter.jmeter.http2.core.ServerBuilder.SERVER_PATH_200_ZSTD; import static com.blazemeter.jmeter.http2.core.ServerBuilder.SERVER_PATH_200_WITH_BODY; import static com.blazemeter.jmeter.http2.core.ServerBuilder.SERVER_PATH_302; +import static com.blazemeter.jmeter.http2.core.ServerBuilder.SERVER_PATH_302_TO_ECHO; import static com.blazemeter.jmeter.http2.core.ServerBuilder.SERVER_PATH_400; import static com.blazemeter.jmeter.http2.core.ServerBuilder.SERVER_PATH_BIG_RESPONSE; import static com.blazemeter.jmeter.http2.core.ServerBuilder.SERVER_PATH_200_DEFLATE; @@ -846,6 +847,27 @@ private void validateRedirects(HTTPSampleResult result, HTTPSampleResult expecte softly.assertThat(result.getRedirectLocation()).isEqualTo(expected.getRedirectLocation()); } + @Test + public void shouldNotResendRequestBodyWhenPostRedirectIsFollowedAsGet() throws Exception { + buildStartedServer(); + sampler.setFollowRedirects(true); + sampler.setMethod(HTTPConstants.POST); + sampler.addArgument("test1", TEST_ARGUMENT_1); + sampler.addArgument("test2", TEST_ARGUMENT_2); + + HTTPSampleResult result = sample(SERVER_PATH_302_TO_ECHO, HTTPConstants.POST); + + softly.assertThat(result.getResponseCode()).isEqualTo("200"); + SampleResult[] subResults = result.getSubResults(); + softly.assertThat(subResults.length).isGreaterThan(0); + HTTPSampleResult followUp = (HTTPSampleResult) subResults[subResults.length - 1]; + softly.assertThat(followUp.getHTTPMethod()).isEqualTo(HTTPConstants.GET); + // The redirect target echoes back any request body it receives. JMeter core rewrites + // POST to GET when following a 302, so the follow-up request must not carry the + // original POST entity (RFC 9110 section 15.4; matches HTTPSamplerProxy behavior). + softly.assertThat(followUp.getResponseDataAsString()).isEmpty(); + } + @Test public void shouldGetOnlyRedirectedResultWhenFollowRedirectDisabledAndRedirected() throws Exception { diff --git a/src/test/java/com/blazemeter/jmeter/http2/core/ServerBuilder.java b/src/test/java/com/blazemeter/jmeter/http2/core/ServerBuilder.java index a091dd29..ad7552f8 100644 --- a/src/test/java/com/blazemeter/jmeter/http2/core/ServerBuilder.java +++ b/src/test/java/com/blazemeter/jmeter/http2/core/ServerBuilder.java @@ -82,6 +82,7 @@ public class ServerBuilder { public static final String SERVER_PATH_BIG_RESPONSE = "/test/big-response"; public static final String SERVER_PATH_400 = "/test/400"; public static final String SERVER_PATH_302 = "/test/302"; + public static final String SERVER_PATH_302_TO_ECHO = "/test/302-to-echo"; public static final String SERVER_PATH_200_WITH_BODY = "/test/body"; public static final String SERVER_PATH_JSON_ONLY = "/test/json-only"; public static final String SERVER_PATH_DELETE_DATA = "/test/delete"; @@ -294,6 +295,11 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) "https://localhost:" + req.getLocalPort() + SERVER_PATH_200); resp.setStatus(HttpStatus.FOUND_302); break; + case SERVER_PATH_302_TO_ECHO: + resp.addHeader(HTTPConstants.HEADER_LOCATION, + "https://localhost:" + req.getLocalPort() + SERVER_PATH_200_WITH_BODY); + resp.setStatus(HttpStatus.FOUND_302); + break; case SERVER_PATH_200_WITH_BODY: String bodyRequest = req.getReader().lines().collect(Collectors.joining()); resp.getWriter().write(bodyRequest);