This is an automated email from the ASF dual-hosted git repository. lhotari pushed a commit to branch branch-4.2 in repository https://gitbox.apache.org/repos/asf/pulsar.git
commit e3b8c4a29c83095e804fb8a4553d2c5c3d711460 Author: Lari Hotari <[email protected]> AuthorDate: Wed Jun 3 11:54:30 2026 +0300 [fix][proxy] Avoid intermittent 502 when admin proxy follows a broker redirect for a request with a body (#25919) (cherry picked from commit 2acee322ef8ace78759677b997a57be454ed2eca) --- .../pulsar/proxy/server/AdminProxyHandler.java | 31 +++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/pulsar-proxy/src/main/java/org/apache/pulsar/proxy/server/AdminProxyHandler.java b/pulsar-proxy/src/main/java/org/apache/pulsar/proxy/server/AdminProxyHandler.java index 7992cd20d11..ba49714b89b 100644 --- a/pulsar-proxy/src/main/java/org/apache/pulsar/proxy/server/AdminProxyHandler.java +++ b/pulsar-proxy/src/main/java/org/apache/pulsar/proxy/server/AdminProxyHandler.java @@ -46,6 +46,7 @@ import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.ProtocolHandlers; import org.eclipse.jetty.client.RedirectProtocolHandler; import org.eclipse.jetty.client.Request; +import org.eclipse.jetty.client.Response; import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP; import org.eclipse.jetty.ee8.proxy.ProxyServlet; import org.eclipse.jetty.http.HttpField; @@ -121,7 +122,7 @@ class AdminProxyHandler extends ProxyServlet { ProtocolHandlers protocolHandlers = httpClient.getProtocolHandlers(); if (protocolHandlers != null) { - protocolHandlers.put(new RedirectProtocolHandler(httpClient)); + protocolHandlers.put(new NonAbortingRedirectProtocolHandler(httpClient)); } httpClient.setIdleTimeout(config.getHttpProxyIdleTimeout()); @@ -129,6 +130,34 @@ class AdminProxyHandler extends ProxyServlet { setTimeout(config.getHttpProxyTimeout()); } + /** + * A {@link RedirectProtocolHandler} that does not abort the in-flight request when a redirect + * response is received. + * + * <p>Jetty's default {@link RedirectProtocolHandler#onSuccess(Response)} aborts a request that + * still has a body to send when a redirect status is received, raising + * {@code HttpRequestException: "Aborting request after receiving a NNN response"}. When a broker + * returns a 307 (to redirect an admin request to the bundle-owner broker) before the proxy has + * finished streaming the request body, that abort can race ahead of the redirect continuation in + * {@link RedirectProtocolHandler#onComplete} and surface to the proxy as a spurious HTTP 502 Bad + * Gateway. The redirect itself is driven by {@code onComplete} from the response (its status and + * {@code Location} header) and does not depend on the abort, so skipping it lets the redirect + * always be followed on a fresh request (with the body replayed by + * {@link ReplayableProxyContentProvider}), which is the behavior this proxy needs. + */ + static class NonAbortingRedirectProtocolHandler extends RedirectProtocolHandler { + NonAbortingRedirectProtocolHandler(HttpClient client) { + super(client); + } + + @Override + public void onSuccess(Response response) { + // Intentionally do NOT abort the request here. The redirect is followed in onComplete(); + // aborting the in-flight request only races a 502 to the proxy when the broker returns a + // redirect before the request body has finished sending. + } + } + // This class allows the request body to be replayed, the default implementation // does not protected class ReplayableProxyContentProvider extends ProxyInputStreamRequestContent {
