Skip to content

Commit 13888f2

Browse files
authored
Fixes for ProxyRequest and ProxyResponse with null Body (#111) (#112)
See #110 In some cases, users want to skip the proxied request or the backend response entirely. To do so, they can set a null Body in ProxyRequest or ProxyResponse. However: - For ProxyRequest, it led to a NPE - For ProxyResponse, the content length was not overwritten if present Signed-off-by: Thomas Segismont <[email protected]>
1 parent 2217655 commit 13888f2

File tree

3 files changed

+87
-26
lines changed

3 files changed

+87
-26
lines changed

src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
2+
* Copyright (c) 2011-2025 Contributors to the Eclipse Foundation
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License 2.0 which is available at
@@ -32,19 +32,27 @@
3232
import java.util.Map;
3333
import java.util.Objects;
3434

35+
import static io.vertx.core.http.HttpHeaders.CONNECTION;
36+
import static io.vertx.core.http.HttpHeaders.CONTENT_LENGTH;
37+
import static io.vertx.core.http.HttpHeaders.KEEP_ALIVE;
38+
import static io.vertx.core.http.HttpHeaders.PROXY_AUTHENTICATE;
39+
import static io.vertx.core.http.HttpHeaders.PROXY_AUTHORIZATION;
40+
import static io.vertx.core.http.HttpHeaders.TRANSFER_ENCODING;
41+
import static io.vertx.core.http.HttpHeaders.UPGRADE;
42+
3543
public class ProxiedRequest implements ProxyRequest {
3644

3745
private static final CharSequence X_FORWARDED_HOST = HttpHeaders.createOptimized("x-forwarded-host");
3846

3947
private static final MultiMap HOP_BY_HOP_HEADERS = MultiMap.caseInsensitiveMultiMap()
40-
.add(HttpHeaders.CONNECTION, "whatever")
41-
.add(HttpHeaders.KEEP_ALIVE, "whatever")
42-
.add(HttpHeaders.PROXY_AUTHENTICATE, "whatever")
43-
.add(HttpHeaders.PROXY_AUTHORIZATION, "whatever")
48+
.add(CONNECTION, "whatever")
49+
.add(KEEP_ALIVE, "whatever")
50+
.add(PROXY_AUTHENTICATE, "whatever")
51+
.add(PROXY_AUTHORIZATION, "whatever")
4452
.add("te", "whatever")
4553
.add("trailer", "whatever")
46-
.add(HttpHeaders.TRANSFER_ENCODING, "whatever")
47-
.add(HttpHeaders.UPGRADE, "whatever");
54+
.add(TRANSFER_ENCODING, "whatever")
55+
.add(UPGRADE, "whatever");
4856

4957
final ContextInternal context;
5058
private HttpMethod method;
@@ -61,7 +69,7 @@ public ProxiedRequest(HttpServerRequest proxiedRequest) {
6169

6270
// Determine content length
6371
long contentLength = -1L;
64-
String contentLengthHeader = proxiedRequest.getHeader(HttpHeaders.CONTENT_LENGTH);
72+
String contentLengthHeader = proxiedRequest.getHeader(CONTENT_LENGTH);
6573
if (contentLengthHeader != null) {
6674
try {
6775
contentLength = Long.parseLong(contentLengthHeader);
@@ -184,22 +192,29 @@ void sendRequest(Handler<AsyncResult<ProxyResponse>> responseHandler) {
184192
}
185193
}
186194

187-
long len = body.length();
188-
if (len >= 0) {
189-
request.putHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(len));
195+
if (body == null) {
196+
if (proxiedRequest.headers().contains(CONTENT_LENGTH)) {
197+
request.putHeader(CONTENT_LENGTH, "0");
198+
}
199+
request.end();
190200
} else {
191-
Boolean isChunked = HttpUtils.isChunked(proxiedRequest.headers());
192-
request.setChunked(len == -1 && Boolean.TRUE == isChunked);
193-
}
194-
195-
Pipe<Buffer> pipe = body.stream().pipe();
196-
pipe.endOnComplete(true);
197-
pipe.endOnFailure(false);
198-
pipe.to(request, ar -> {
199-
if (ar.failed()) {
200-
request.reset();
201+
long len = body.length();
202+
if (len >= 0) {
203+
request.putHeader(CONTENT_LENGTH, Long.toString(len));
204+
} else {
205+
Boolean isChunked = HttpUtils.isChunked(proxiedRequest.headers());
206+
request.setChunked(len == -1 && Boolean.TRUE == isChunked);
201207
}
202-
});
208+
209+
Pipe<Buffer> pipe = body.stream().pipe();
210+
pipe.endOnComplete(true);
211+
pipe.endOnFailure(false);
212+
pipe.to(request, ar -> {
213+
if (ar.failed()) {
214+
request.reset();
215+
}
216+
});
217+
}
203218
}
204219

205220
private static boolean equals(HostAndPort hp1, HostAndPort hp2) {

src/main/java/io/vertx/httpproxy/impl/ProxiedResponse.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
2+
* Copyright (c) 2011-2025 Contributors to the Eclipse Foundation
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License 2.0 which is available at
@@ -31,6 +31,8 @@
3131
import java.util.Iterator;
3232
import java.util.List;
3333

34+
import static io.vertx.core.http.HttpHeaders.CONTENT_LENGTH;
35+
3436
class ProxiedResponse implements ProxyResponse {
3537

3638
private final ProxiedRequest request;
@@ -215,9 +217,11 @@ public void send(Handler<AsyncResult<Void>> completionHandler) {
215217
}
216218
});
217219

218-
//
219220
if (body == null) {
220-
proxiedResponse.end();
221+
if (response != null && response.headers().contains(CONTENT_LENGTH)) {
222+
proxiedResponse.putHeader(CONTENT_LENGTH, "0");
223+
}
224+
proxiedResponse.end().onComplete(completionHandler);
221225
return;
222226
}
223227

src/test/java/io/vertx/httpproxy/ProxyTest.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
2+
* Copyright (c) 2011-2025 Contributors to the Eclipse Foundation
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License 2.0 which is available at
@@ -27,6 +27,8 @@
2727
import java.util.Map;
2828
import java.util.concurrent.atomic.AtomicInteger;
2929

30+
import static io.vertx.core.http.HttpHeaders.CONTENT_LENGTH;
31+
3032
/**
3133
* @author <a href="mailto:[email protected]">Julien Viet</a>
3234
*/
@@ -103,6 +105,46 @@ public Future<Void> handleProxyResponse(ProxyContext context) {
103105
});
104106
}
105107

108+
@Test
109+
public void testFilterNullBodies(TestContext ctx) {
110+
Async latch = ctx.async(3);
111+
SocketAddress backend = startHttpBackend(ctx, 8081, req -> {
112+
req.body().onComplete(ctx.asyncAssertSuccess(body -> {
113+
ctx.assertEquals(0, body.length());
114+
ctx.assertEquals("0", req.getHeader(CONTENT_LENGTH));
115+
req.response().end("IGNORED_BACKEND_RESPONSE_BODY");
116+
}));
117+
});
118+
startProxy(proxy -> proxy.origin(backend).addInterceptor(new ProxyInterceptor() {
119+
@Override
120+
public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
121+
context.request().setBody(null);
122+
Future<ProxyResponse> fut = context.sendRequest();
123+
fut.onComplete(ctx.asyncAssertSuccess(v -> latch.countDown()));
124+
return fut;
125+
}
126+
127+
@Override
128+
public Future<Void> handleProxyResponse(ProxyContext context) {
129+
context.response().setBody(null);
130+
Future<Void> fut = context.sendResponse();
131+
fut.onComplete(ctx.asyncAssertSuccess(v -> latch.countDown()));
132+
return fut;
133+
}
134+
}));
135+
HttpClient client = vertx.createHttpClient();
136+
client
137+
.request(HttpMethod.POST, 8080, "localhost", "/")
138+
.compose(req -> req
139+
.send("IGNORED_CLIENT_REQUEST_BODY")
140+
.compose(resp -> resp.body().map(resp))
141+
).onComplete(ctx.asyncAssertSuccess(resp -> {
142+
ctx.assertEquals(0, resp.body().result().length());
143+
ctx.assertEquals("0", resp.getHeader(CONTENT_LENGTH));
144+
latch.countDown();
145+
}));
146+
}
147+
106148
@Test
107149
public void testUpstreamRefuse(TestContext ctx) {
108150
SocketAddress backend = SocketAddress.inetSocketAddress(8081, "localhost");

0 commit comments

Comments
 (0)