diff --git a/src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java b/src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java index 12a4087..3686e56 100644 --- a/src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java +++ b/src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2020 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2025 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -10,9 +10,14 @@ */ package io.vertx.httpproxy.impl; -import io.vertx.core.*; +import io.vertx.core.Future; +import io.vertx.core.MultiMap; import io.vertx.core.buffer.Buffer; -import io.vertx.core.http.*; +import io.vertx.core.http.HttpClientRequest; +import io.vertx.core.http.HttpHeaders; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.http.HttpVersion; import io.vertx.core.internal.ContextInternal; import io.vertx.core.internal.http.HttpServerRequestInternal; import io.vertx.core.net.HostAndPort; @@ -24,19 +29,27 @@ import java.util.Map; import java.util.Objects; +import static io.vertx.core.http.HttpHeaders.CONNECTION; +import static io.vertx.core.http.HttpHeaders.CONTENT_LENGTH; +import static io.vertx.core.http.HttpHeaders.KEEP_ALIVE; +import static io.vertx.core.http.HttpHeaders.PROXY_AUTHENTICATE; +import static io.vertx.core.http.HttpHeaders.PROXY_AUTHORIZATION; +import static io.vertx.core.http.HttpHeaders.TRANSFER_ENCODING; +import static io.vertx.core.http.HttpHeaders.UPGRADE; + public class ProxiedRequest implements ProxyRequest { private static final CharSequence X_FORWARDED_HOST = HttpHeaders.createOptimized("x-forwarded-host"); private static final MultiMap HOP_BY_HOP_HEADERS = MultiMap.caseInsensitiveMultiMap() - .add(HttpHeaders.CONNECTION, "whatever") - .add(HttpHeaders.KEEP_ALIVE, "whatever") - .add(HttpHeaders.PROXY_AUTHENTICATE, "whatever") - .add(HttpHeaders.PROXY_AUTHORIZATION, "whatever") + .add(CONNECTION, "whatever") + .add(KEEP_ALIVE, "whatever") + .add(PROXY_AUTHENTICATE, "whatever") + .add(PROXY_AUTHORIZATION, "whatever") .add("te", "whatever") .add("trailer", "whatever") - .add(HttpHeaders.TRANSFER_ENCODING, "whatever") - .add(HttpHeaders.UPGRADE, "whatever"); + .add(TRANSFER_ENCODING, "whatever") + .add(UPGRADE, "whatever"); final ContextInternal context; private HttpMethod method; @@ -53,7 +66,7 @@ public ProxiedRequest(HttpServerRequest proxiedRequest) { // Determine content length long contentLength = -1L; - String contentLengthHeader = proxiedRequest.getHeader(HttpHeaders.CONTENT_LENGTH); + String contentLengthHeader = proxiedRequest.getHeader(CONTENT_LENGTH); if (contentLengthHeader != null) { try { contentLength = Long.parseLong(contentLengthHeader); @@ -170,24 +183,31 @@ Future sendRequest() { } } - long len = body.length(); - if (len >= 0) { - request.putHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(len)); + if (body == null) { + if (proxiedRequest.headers().contains(CONTENT_LENGTH)) { + request.putHeader(CONTENT_LENGTH, "0"); + } + request.end(); } else { - Boolean isChunked = HttpUtils.isChunked(proxiedRequest.headers()); - request.setChunked(len == -1 && Boolean.TRUE == isChunked); - } - - Pipe pipe = body.stream().pipe(); - pipe.endOnComplete(true); - pipe.endOnFailure(false); - pipe.to(request).onComplete(ar -> { - if (ar.failed()) { - request.reset(); + long len = body.length(); + if (len >= 0) { + request.putHeader(CONTENT_LENGTH, Long.toString(len)); + } else { + Boolean isChunked = HttpUtils.isChunked(proxiedRequest.headers()); + request.setChunked(len == -1 && Boolean.TRUE == isChunked); } - }); - return request.response().map(r -> { + Pipe pipe = body.stream().pipe(); + pipe.endOnComplete(true); + pipe.endOnFailure(false); + pipe.to(request).onComplete(ar -> { + if (ar.failed()) { + request.reset(); + } + }); + } + + return request.response().map(r -> { r.pause(); // Pause it return new ProxiedResponse(this, proxiedRequest.response(), r); }); diff --git a/src/main/java/io/vertx/httpproxy/impl/ProxiedResponse.java b/src/main/java/io/vertx/httpproxy/impl/ProxiedResponse.java index cf81268..f360daf 100644 --- a/src/main/java/io/vertx/httpproxy/impl/ProxiedResponse.java +++ b/src/main/java/io/vertx/httpproxy/impl/ProxiedResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2020 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2025 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -10,11 +10,8 @@ */ package io.vertx.httpproxy.impl; -import io.vertx.core.AsyncResult; import io.vertx.core.Future; -import io.vertx.core.Handler; import io.vertx.core.MultiMap; -import io.vertx.core.Promise; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.HttpClientResponse; import io.vertx.core.http.HttpHeaders; @@ -31,6 +28,8 @@ import java.util.Iterator; import java.util.List; +import static io.vertx.core.http.HttpHeaders.CONTENT_LENGTH; + class ProxiedResponse implements ProxyResponse { private final ProxiedRequest request; @@ -214,8 +213,10 @@ public Future send() { } }); - // if (body == null) { + if (response != null && response.headers().contains(CONTENT_LENGTH)) { + proxiedResponse.putHeader(CONTENT_LENGTH, "0"); + } return proxiedResponse.end(); } else { long len = body.length(); diff --git a/src/test/java/io/vertx/tests/ProxyTest.java b/src/test/java/io/vertx/tests/ProxyTest.java index 582ce50..62c0e23 100644 --- a/src/test/java/io/vertx/tests/ProxyTest.java +++ b/src/test/java/io/vertx/tests/ProxyTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2020 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2025 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -31,6 +31,8 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import static io.vertx.core.http.HttpHeaders.CONTENT_LENGTH; + /** * @author Julien Viet */ @@ -115,6 +117,46 @@ public Future handleProxyResponse(ProxyContext context) { }); } + @Test + public void testFilterNullBodies(TestContext ctx) { + Async latch = ctx.async(3); + SocketAddress backend = startHttpBackend(ctx, 8081, req -> { + req.body().onComplete(ctx.asyncAssertSuccess(body -> { + ctx.assertEquals(0, body.length()); + ctx.assertEquals("0", req.getHeader(CONTENT_LENGTH)); + req.response().end("IGNORED_BACKEND_RESPONSE_BODY"); + })); + }); + startProxy(proxy -> proxy.origin(backend).addInterceptor(new ProxyInterceptor() { + @Override + public Future handleProxyRequest(ProxyContext context) { + context.request().setBody(null); + Future fut = context.sendRequest(); + fut.onComplete(ctx.asyncAssertSuccess(v -> latch.countDown())); + return fut; + } + + @Override + public Future handleProxyResponse(ProxyContext context) { + context.response().setBody(null); + Future fut = context.sendResponse(); + fut.onComplete(ctx.asyncAssertSuccess(v -> latch.countDown())); + return fut; + } + })); + client = vertx.createHttpClient(); + client + .request(HttpMethod.POST, 8080, "localhost", "/") + .compose(req -> req + .send("IGNORED_CLIENT_REQUEST_BODY") + .compose(resp -> resp.body().map(resp)) + ).onComplete(ctx.asyncAssertSuccess(resp -> { + ctx.assertEquals(0, resp.body().result().length()); + ctx.assertEquals("0", resp.getHeader(CONTENT_LENGTH)); + latch.countDown(); + })); + } + @Test public void testUpstreamRefuse(TestContext ctx) { SocketAddress backend = SocketAddress.inetSocketAddress(8081, "localhost");