Skip to content

Commit

Permalink
Fixes for ProxyRequest and ProxyResponse with null Body (#111)
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
tsegismont authored Jan 22, 2025
1 parent 5fc5b87 commit de37cc1
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 31 deletions.
70 changes: 45 additions & 25 deletions src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -170,24 +183,31 @@ Future<ProxyResponse> 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<Buffer> 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().<ProxyResponse>map(r -> {
Pipe<Buffer> 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);
});
Expand Down
11 changes: 6 additions & 5 deletions src/main/java/io/vertx/httpproxy/impl/ProxiedResponse.java
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -214,8 +213,10 @@ public Future<Void> 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();
Expand Down
44 changes: 43 additions & 1 deletion src/test/java/io/vertx/tests/ProxyTest.java
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -31,6 +31,8 @@
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import static io.vertx.core.http.HttpHeaders.CONTENT_LENGTH;

/**
* @author <a href="mailto:[email protected]">Julien Viet</a>
*/
Expand Down Expand Up @@ -115,6 +117,46 @@ public Future<Void> 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<ProxyResponse> handleProxyRequest(ProxyContext context) {
context.request().setBody(null);
Future<ProxyResponse> fut = context.sendRequest();
fut.onComplete(ctx.asyncAssertSuccess(v -> latch.countDown()));
return fut;
}

@Override
public Future<Void> handleProxyResponse(ProxyContext context) {
context.response().setBody(null);
Future<Void> 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");
Expand Down

0 comments on commit de37cc1

Please sign in to comment.