Skip to content

Commit de37cc1

Browse files
authored
Fixes for ProxyRequest and ProxyResponse with null Body (#111)
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 <tsegismont@gmail.com>
1 parent 5fc5b87 commit de37cc1

File tree

3 files changed

+94
-31
lines changed

3 files changed

+94
-31
lines changed

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

Lines changed: 45 additions & 25 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
@@ -10,9 +10,14 @@
1010
*/
1111
package io.vertx.httpproxy.impl;
1212

13-
import io.vertx.core.*;
13+
import io.vertx.core.Future;
14+
import io.vertx.core.MultiMap;
1415
import io.vertx.core.buffer.Buffer;
15-
import io.vertx.core.http.*;
16+
import io.vertx.core.http.HttpClientRequest;
17+
import io.vertx.core.http.HttpHeaders;
18+
import io.vertx.core.http.HttpMethod;
19+
import io.vertx.core.http.HttpServerRequest;
20+
import io.vertx.core.http.HttpVersion;
1621
import io.vertx.core.internal.ContextInternal;
1722
import io.vertx.core.internal.http.HttpServerRequestInternal;
1823
import io.vertx.core.net.HostAndPort;
@@ -24,19 +29,27 @@
2429
import java.util.Map;
2530
import java.util.Objects;
2631

32+
import static io.vertx.core.http.HttpHeaders.CONNECTION;
33+
import static io.vertx.core.http.HttpHeaders.CONTENT_LENGTH;
34+
import static io.vertx.core.http.HttpHeaders.KEEP_ALIVE;
35+
import static io.vertx.core.http.HttpHeaders.PROXY_AUTHENTICATE;
36+
import static io.vertx.core.http.HttpHeaders.PROXY_AUTHORIZATION;
37+
import static io.vertx.core.http.HttpHeaders.TRANSFER_ENCODING;
38+
import static io.vertx.core.http.HttpHeaders.UPGRADE;
39+
2740
public class ProxiedRequest implements ProxyRequest {
2841

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

3144
private static final MultiMap HOP_BY_HOP_HEADERS = MultiMap.caseInsensitiveMultiMap()
32-
.add(HttpHeaders.CONNECTION, "whatever")
33-
.add(HttpHeaders.KEEP_ALIVE, "whatever")
34-
.add(HttpHeaders.PROXY_AUTHENTICATE, "whatever")
35-
.add(HttpHeaders.PROXY_AUTHORIZATION, "whatever")
45+
.add(CONNECTION, "whatever")
46+
.add(KEEP_ALIVE, "whatever")
47+
.add(PROXY_AUTHENTICATE, "whatever")
48+
.add(PROXY_AUTHORIZATION, "whatever")
3649
.add("te", "whatever")
3750
.add("trailer", "whatever")
38-
.add(HttpHeaders.TRANSFER_ENCODING, "whatever")
39-
.add(HttpHeaders.UPGRADE, "whatever");
51+
.add(TRANSFER_ENCODING, "whatever")
52+
.add(UPGRADE, "whatever");
4053

4154
final ContextInternal context;
4255
private HttpMethod method;
@@ -53,7 +66,7 @@ public ProxiedRequest(HttpServerRequest proxiedRequest) {
5366

5467
// Determine content length
5568
long contentLength = -1L;
56-
String contentLengthHeader = proxiedRequest.getHeader(HttpHeaders.CONTENT_LENGTH);
69+
String contentLengthHeader = proxiedRequest.getHeader(CONTENT_LENGTH);
5770
if (contentLengthHeader != null) {
5871
try {
5972
contentLength = Long.parseLong(contentLengthHeader);
@@ -170,24 +183,31 @@ Future<ProxyResponse> sendRequest() {
170183
}
171184
}
172185

173-
long len = body.length();
174-
if (len >= 0) {
175-
request.putHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(len));
186+
if (body == null) {
187+
if (proxiedRequest.headers().contains(CONTENT_LENGTH)) {
188+
request.putHeader(CONTENT_LENGTH, "0");
189+
}
190+
request.end();
176191
} else {
177-
Boolean isChunked = HttpUtils.isChunked(proxiedRequest.headers());
178-
request.setChunked(len == -1 && Boolean.TRUE == isChunked);
179-
}
180-
181-
Pipe<Buffer> pipe = body.stream().pipe();
182-
pipe.endOnComplete(true);
183-
pipe.endOnFailure(false);
184-
pipe.to(request).onComplete(ar -> {
185-
if (ar.failed()) {
186-
request.reset();
192+
long len = body.length();
193+
if (len >= 0) {
194+
request.putHeader(CONTENT_LENGTH, Long.toString(len));
195+
} else {
196+
Boolean isChunked = HttpUtils.isChunked(proxiedRequest.headers());
197+
request.setChunked(len == -1 && Boolean.TRUE == isChunked);
187198
}
188-
});
189199

190-
return request.response().<ProxyResponse>map(r -> {
200+
Pipe<Buffer> pipe = body.stream().pipe();
201+
pipe.endOnComplete(true);
202+
pipe.endOnFailure(false);
203+
pipe.to(request).onComplete(ar -> {
204+
if (ar.failed()) {
205+
request.reset();
206+
}
207+
});
208+
}
209+
210+
return request.response().map(r -> {
191211
r.pause(); // Pause it
192212
return new ProxiedResponse(this, proxiedRequest.response(), r);
193213
});

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

Lines changed: 6 additions & 5 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
@@ -10,11 +10,8 @@
1010
*/
1111
package io.vertx.httpproxy.impl;
1212

13-
import io.vertx.core.AsyncResult;
1413
import io.vertx.core.Future;
15-
import io.vertx.core.Handler;
1614
import io.vertx.core.MultiMap;
17-
import io.vertx.core.Promise;
1815
import io.vertx.core.buffer.Buffer;
1916
import io.vertx.core.http.HttpClientResponse;
2017
import io.vertx.core.http.HttpHeaders;
@@ -31,6 +28,8 @@
3128
import java.util.Iterator;
3229
import java.util.List;
3330

31+
import static io.vertx.core.http.HttpHeaders.CONTENT_LENGTH;
32+
3433
class ProxiedResponse implements ProxyResponse {
3534

3635
private final ProxiedRequest request;
@@ -214,8 +213,10 @@ public Future<Void> send() {
214213
}
215214
});
216215

217-
//
218216
if (body == null) {
217+
if (response != null && response.headers().contains(CONTENT_LENGTH)) {
218+
proxiedResponse.putHeader(CONTENT_LENGTH, "0");
219+
}
219220
return proxiedResponse.end();
220221
} else {
221222
long len = body.length();

src/test/java/io/vertx/tests/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
@@ -31,6 +31,8 @@
3131
import java.util.Map;
3232
import java.util.concurrent.atomic.AtomicInteger;
3333

34+
import static io.vertx.core.http.HttpHeaders.CONTENT_LENGTH;
35+
3436
/**
3537
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
3638
*/
@@ -115,6 +117,46 @@ public Future<Void> handleProxyResponse(ProxyContext context) {
115117
});
116118
}
117119

120+
@Test
121+
public void testFilterNullBodies(TestContext ctx) {
122+
Async latch = ctx.async(3);
123+
SocketAddress backend = startHttpBackend(ctx, 8081, req -> {
124+
req.body().onComplete(ctx.asyncAssertSuccess(body -> {
125+
ctx.assertEquals(0, body.length());
126+
ctx.assertEquals("0", req.getHeader(CONTENT_LENGTH));
127+
req.response().end("IGNORED_BACKEND_RESPONSE_BODY");
128+
}));
129+
});
130+
startProxy(proxy -> proxy.origin(backend).addInterceptor(new ProxyInterceptor() {
131+
@Override
132+
public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
133+
context.request().setBody(null);
134+
Future<ProxyResponse> fut = context.sendRequest();
135+
fut.onComplete(ctx.asyncAssertSuccess(v -> latch.countDown()));
136+
return fut;
137+
}
138+
139+
@Override
140+
public Future<Void> handleProxyResponse(ProxyContext context) {
141+
context.response().setBody(null);
142+
Future<Void> fut = context.sendResponse();
143+
fut.onComplete(ctx.asyncAssertSuccess(v -> latch.countDown()));
144+
return fut;
145+
}
146+
}));
147+
client = vertx.createHttpClient();
148+
client
149+
.request(HttpMethod.POST, 8080, "localhost", "/")
150+
.compose(req -> req
151+
.send("IGNORED_CLIENT_REQUEST_BODY")
152+
.compose(resp -> resp.body().map(resp))
153+
).onComplete(ctx.asyncAssertSuccess(resp -> {
154+
ctx.assertEquals(0, resp.body().result().length());
155+
ctx.assertEquals("0", resp.getHeader(CONTENT_LENGTH));
156+
latch.countDown();
157+
}));
158+
}
159+
118160
@Test
119161
public void testUpstreamRefuse(TestContext ctx) {
120162
SocketAddress backend = SocketAddress.inetSocketAddress(8081, "localhost");

0 commit comments

Comments
 (0)