From d802d7e9367e82c3e25573e89582cc561344d3b2 Mon Sep 17 00:00:00 2001 From: Yash Shinde Date: Fri, 10 Oct 2025 18:56:22 +0530 Subject: [PATCH 1/2] Added support to specify Custom Hop-By-Hop Headers --- .../java/io/vertx/httpproxy/ProxyOptions.java | 43 +++++++++++++++ .../java/io/vertx/httpproxy/ProxyRequest.java | 13 +++++ .../vertx/httpproxy/impl/ProxiedRequest.java | 53 ++++++++++++------- .../io/vertx/httpproxy/impl/ReverseProxy.java | 4 +- 4 files changed, 94 insertions(+), 19 deletions(-) diff --git a/src/main/java/io/vertx/httpproxy/ProxyOptions.java b/src/main/java/io/vertx/httpproxy/ProxyOptions.java index bae6bed..8846c8e 100644 --- a/src/main/java/io/vertx/httpproxy/ProxyOptions.java +++ b/src/main/java/io/vertx/httpproxy/ProxyOptions.java @@ -4,6 +4,12 @@ import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.json.JsonObject; import io.vertx.httpproxy.cache.CacheOptions; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static io.vertx.core.http.HttpHeaders.*; +import static io.vertx.core.http.HttpHeaders.UPGRADE; /** * Proxy options. @@ -17,8 +23,20 @@ public class ProxyOptions { */ public static final boolean DEFAULT_SUPPORT_WEBSOCKET = true; + public static final List DEFAULT_HOP_BY_HOP_HEADERS = new ArrayList<>(Arrays.asList( + CONNECTION.toString(), + KEEP_ALIVE.toString(), + PROXY_AUTHENTICATE.toString(), + PROXY_AUTHORIZATION.toString(), + TRANSFER_ENCODING.toString(), + UPGRADE.toString(), + "te", + "trailer" + )); + private CacheOptions cacheOptions; private boolean supportWebSocket; + private List customHopHeaders; public ProxyOptions(JsonObject json) { ProxyOptionsConverter.fromJson(json, this); @@ -26,6 +44,7 @@ public ProxyOptions(JsonObject json) { public ProxyOptions() { supportWebSocket = DEFAULT_SUPPORT_WEBSOCKET; + customHopHeaders = DEFAULT_HOP_BY_HOP_HEADERS; } /** @@ -66,6 +85,30 @@ public ProxyOptions setSupportWebSocket(boolean supportWebSocket) { return this; } + /** + * @return custom hop-by-hop headers + */ + public List getCustomHopHeaders() { + return customHopHeaders; + } + + /** + * Sets custom hop-by-hop headers, overriding the default {@code DEFAULT_HOP_BY_HOP_HEADERS} headers. + *

+ * Warning: Please read the following specification before removing or modifying + * any hop-by-hop header: + * + * RFC 2616, Section 13.5.1 + * + *

+ * + * @param customHopHeaders the list of hop-by-hop headers to set + */ + public ProxyOptions setCustomHopHeaders(List customHopHeaders){ + this.customHopHeaders = customHopHeaders; + return this; + } + @Override public String toString() { return toJson().toString(); diff --git a/src/main/java/io/vertx/httpproxy/ProxyRequest.java b/src/main/java/io/vertx/httpproxy/ProxyRequest.java index 30d96d5..3d6e676 100644 --- a/src/main/java/io/vertx/httpproxy/ProxyRequest.java +++ b/src/main/java/io/vertx/httpproxy/ProxyRequest.java @@ -21,6 +21,7 @@ import io.vertx.core.http.HttpVersion; import io.vertx.core.net.HostAndPort; import io.vertx.httpproxy.impl.ProxiedRequest; +import java.util.List; /** * @@ -42,6 +43,18 @@ static ProxyRequest reverseProxy(HttpServerRequest proxiedRequest) { return new ProxiedRequest(proxiedRequest); } + /** + * Create a new {@code ProxyRequest} instance, the proxied request will be paused. + * + * @param proxiedRequest the {@code HttpServerRequest} that is proxied + * @param customHopHeaders list of Custom Hop-By-Hop Headers + * @return a reference to this, so the API can be used fluently + */ + static ProxyRequest reverseProxy(HttpServerRequest proxiedRequest, List customHopHeaders) { + proxiedRequest.pause(); + return new ProxiedRequest(proxiedRequest, customHopHeaders); + } + /** * @return the HTTP version of the proxied request */ diff --git a/src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java b/src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java index 29926f5..9cb1335 100644 --- a/src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java +++ b/src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java @@ -23,35 +23,23 @@ import io.vertx.core.net.HostAndPort; import io.vertx.core.streams.Pipe; import io.vertx.httpproxy.Body; -import io.vertx.httpproxy.MediaType; import io.vertx.httpproxy.ProxyRequest; import io.vertx.httpproxy.ProxyResponse; - +import io.vertx.httpproxy.ProxyOptions; import java.util.Map; import java.util.Objects; +import java.util.HashSet; +import java.util.List; -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(CONNECTION, "whatever") - .add(KEEP_ALIVE, "whatever") - .add(PROXY_AUTHENTICATE, "whatever") - .add(PROXY_AUTHORIZATION, "whatever") - .add("te", "whatever") - .add("trailer", "whatever") - .add(TRANSFER_ENCODING, "whatever") - .add(UPGRADE, "whatever"); + private static final HashSet DEFAULT_HOP_BY_HOP_HEADERS = new HashSet<>(ProxyOptions.DEFAULT_HOP_BY_HOP_HEADERS); + private final HashSet HOP_BY_HOP_HEADERS; final ContextInternal context; private HttpMethod method; private final HttpVersion version; @@ -88,6 +76,35 @@ public ProxiedRequest(HttpServerRequest proxiedRequest) { this.proxiedRequest = proxiedRequest; this.context = ((HttpServerRequestInternal) proxiedRequest).context(); this.authority = null; // null is used as a signal to indicate an unchanged authority + this.HOP_BY_HOP_HEADERS = DEFAULT_HOP_BY_HOP_HEADERS; + } + + public ProxiedRequest(HttpServerRequest proxiedRequest, List customHopHeaders) { + + // Determine content length + long contentLength = -1L; + String contentLengthHeader = proxiedRequest.getHeader(CONTENT_LENGTH); + if (contentLengthHeader != null) { + try { + contentLength = Long.parseLong(contentLengthHeader); + } catch (NumberFormatException e) { + // Ignore ??? + } + } + + // Content type + String contentType = proxiedRequest.getHeader(HttpHeaders.CONTENT_TYPE); + + this.method = proxiedRequest.method(); + this.version = proxiedRequest.version(); + this.body = Body.body(proxiedRequest, contentLength, contentType); + this.uri = proxiedRequest.uri(); + this.headers = MultiMap.caseInsensitiveMultiMap().addAll(proxiedRequest.headers()); + this.absoluteURI = proxiedRequest.absoluteURI(); + this.proxiedRequest = proxiedRequest; + this.context = ((HttpServerRequestInternal) proxiedRequest).context(); + this.authority = null; // null is used as a signal to indicate an unchanged authority + this.HOP_BY_HOP_HEADERS = new HashSet<>(customHopHeaders); } @Override @@ -173,7 +190,7 @@ Future sendRequest() { for (Map.Entry header : headers) { String name = header.getKey(); String value = header.getValue(); - if (!HOP_BY_HOP_HEADERS.contains(name) && !name.equalsIgnoreCase(HttpHeaders.HOST.toString())) { + if (!HOP_BY_HOP_HEADERS.contains(name)) { request.headers().add(name, value); } } diff --git a/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java b/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java index cda186f..cbffa37 100644 --- a/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java +++ b/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java @@ -38,6 +38,7 @@ public class ReverseProxy implements HttpProxy { private final boolean supportWebSocket; private OriginRequestProvider originRequestProvider = (pc) -> Future.failedFuture("No origin available"); private final List interceptors = new ArrayList<>(); + private final List hopByHopHeaders; public ReverseProxy(ProxyOptions options, HttpClient client) { CacheOptions cacheOptions = options.getCacheOptions(); @@ -47,6 +48,7 @@ public ReverseProxy(ProxyOptions options, HttpClient client) { } this.client = client; this.supportWebSocket = options.getSupportWebSocket(); + this.hopByHopHeaders = options.getCustomHopHeaders(); } public Cache newCache(CacheOptions options, Vertx vertx) { @@ -73,7 +75,7 @@ public HttpProxy addInterceptor(ProxyInterceptor interceptor, boolean supportsWe @Override public void handle(HttpServerRequest request) { - ProxyRequest proxyRequest = ProxyRequest.reverseProxy(request); + ProxyRequest proxyRequest = ProxyRequest.reverseProxy(request, hopByHopHeaders); // Encoding sanity check Boolean chunked = HttpUtils.isChunked(request.headers()); From 1f8fa9bee17ee28f55d4b31ba8b5f09d478759c8 Mon Sep 17 00:00:00 2001 From: Yash Shinde Date: Mon, 13 Oct 2025 10:29:36 +0530 Subject: [PATCH 2/2] Replaced List with Set for custom hop headers, Added a function addCustomHopHeader to allow adding headers to current hop headers set --- .../java/io/vertx/httpproxy/ProxyOptions.java | 35 ++++++++++---- .../java/io/vertx/httpproxy/ProxyRequest.java | 30 +++++++----- .../vertx/httpproxy/impl/ProxiedRequest.java | 47 ++++++------------- .../io/vertx/httpproxy/impl/ReverseProxy.java | 8 ++-- 4 files changed, 62 insertions(+), 58 deletions(-) diff --git a/src/main/java/io/vertx/httpproxy/ProxyOptions.java b/src/main/java/io/vertx/httpproxy/ProxyOptions.java index 8846c8e..f1b8683 100644 --- a/src/main/java/io/vertx/httpproxy/ProxyOptions.java +++ b/src/main/java/io/vertx/httpproxy/ProxyOptions.java @@ -4,15 +4,20 @@ import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.json.JsonObject; import io.vertx.httpproxy.cache.CacheOptions; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; - -import static io.vertx.core.http.HttpHeaders.*; +import java.util.HashSet; +import java.util.Set; + +import static io.vertx.core.http.HttpHeaders.CONNECTION; +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; /** * Proxy options. + * */ @DataObject @JsonGen(publicConverter = false) @@ -23,7 +28,7 @@ public class ProxyOptions { */ public static final boolean DEFAULT_SUPPORT_WEBSOCKET = true; - public static final List DEFAULT_HOP_BY_HOP_HEADERS = new ArrayList<>(Arrays.asList( + public static final Set DEFAULT_HOP_BY_HOP_HEADERS = new HashSet<>(Arrays.asList( CONNECTION.toString(), KEEP_ALIVE.toString(), PROXY_AUTHENTICATE.toString(), @@ -36,7 +41,7 @@ public class ProxyOptions { private CacheOptions cacheOptions; private boolean supportWebSocket; - private List customHopHeaders; + private Set customHopHeaders; public ProxyOptions(JsonObject json) { ProxyOptionsConverter.fromJson(json, this); @@ -88,7 +93,7 @@ public ProxyOptions setSupportWebSocket(boolean supportWebSocket) { /** * @return custom hop-by-hop headers */ - public List getCustomHopHeaders() { + public Set getCustomHopHeaders() { return customHopHeaders; } @@ -103,9 +108,21 @@ public List getCustomHopHeaders() { *

* * @param customHopHeaders the list of hop-by-hop headers to set + * @return a reference to this, so the API can be used fluently + */ + public ProxyOptions setCustomHopHeaders(Set customHopHeaders){ + this.customHopHeaders = new HashSet(customHopHeaders); + return this; + } + + /** + * Add a custom hop-by-hop header + * + * @param customHopHeader a custom hop-by-hop header + * @return a reference to this, so the API can be used fluently */ - public ProxyOptions setCustomHopHeaders(List customHopHeaders){ - this.customHopHeaders = customHopHeaders; + public ProxyOptions addCustomHopHeader(String customHopHeader) { + this.customHopHeaders.add(customHopHeader); return this; } diff --git a/src/main/java/io/vertx/httpproxy/ProxyRequest.java b/src/main/java/io/vertx/httpproxy/ProxyRequest.java index 3d6e676..3c20351 100644 --- a/src/main/java/io/vertx/httpproxy/ProxyRequest.java +++ b/src/main/java/io/vertx/httpproxy/ProxyRequest.java @@ -21,7 +21,7 @@ import io.vertx.core.http.HttpVersion; import io.vertx.core.net.HostAndPort; import io.vertx.httpproxy.impl.ProxiedRequest; -import java.util.List; +import java.util.Set; /** * @@ -43,18 +43,6 @@ static ProxyRequest reverseProxy(HttpServerRequest proxiedRequest) { return new ProxiedRequest(proxiedRequest); } - /** - * Create a new {@code ProxyRequest} instance, the proxied request will be paused. - * - * @param proxiedRequest the {@code HttpServerRequest} that is proxied - * @param customHopHeaders list of Custom Hop-By-Hop Headers - * @return a reference to this, so the API can be used fluently - */ - static ProxyRequest reverseProxy(HttpServerRequest proxiedRequest, List customHopHeaders) { - proxiedRequest.pause(); - return new ProxiedRequest(proxiedRequest, customHopHeaders); - } - /** * @return the HTTP version of the proxied request */ @@ -191,4 +179,20 @@ default Future proxy(HttpClientRequest request) { */ ProxyResponse response(); + /** + * Set Custom Hop-By-Hop Headers + * + * @return a reference to this, so the API can be used fluently + */ + @Fluent + ProxyRequest setCustomHopHeaders(Set customHopHeaders); + + /** + * Add Custom Hop-By-Hop Header + * + * @return a reference to this, so the API can be used fluently + */ + @Fluent + ProxyRequest addCustomHopHeader(String customHopHeader); + } diff --git a/src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java b/src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java index 9cb1335..b9c40a9 100644 --- a/src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java +++ b/src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java @@ -29,17 +29,16 @@ import java.util.Map; import java.util.Objects; import java.util.HashSet; -import java.util.List; - +import java.util.Set; import static io.vertx.core.http.HttpHeaders.CONTENT_LENGTH; public class ProxiedRequest implements ProxyRequest { private static final CharSequence X_FORWARDED_HOST = HttpHeaders.createOptimized("x-forwarded-host"); - private static final HashSet DEFAULT_HOP_BY_HOP_HEADERS = new HashSet<>(ProxyOptions.DEFAULT_HOP_BY_HOP_HEADERS); + private static final Set DEFAULT_HOP_BY_HOP_HEADERS = new HashSet<>(ProxyOptions.DEFAULT_HOP_BY_HOP_HEADERS); - private final HashSet HOP_BY_HOP_HEADERS; + private Set HOP_BY_HOP_HEADERS; final ContextInternal context; private HttpMethod method; private final HttpVersion version; @@ -79,34 +78,6 @@ public ProxiedRequest(HttpServerRequest proxiedRequest) { this.HOP_BY_HOP_HEADERS = DEFAULT_HOP_BY_HOP_HEADERS; } - public ProxiedRequest(HttpServerRequest proxiedRequest, List customHopHeaders) { - - // Determine content length - long contentLength = -1L; - String contentLengthHeader = proxiedRequest.getHeader(CONTENT_LENGTH); - if (contentLengthHeader != null) { - try { - contentLength = Long.parseLong(contentLengthHeader); - } catch (NumberFormatException e) { - // Ignore ??? - } - } - - // Content type - String contentType = proxiedRequest.getHeader(HttpHeaders.CONTENT_TYPE); - - this.method = proxiedRequest.method(); - this.version = proxiedRequest.version(); - this.body = Body.body(proxiedRequest, contentLength, contentType); - this.uri = proxiedRequest.uri(); - this.headers = MultiMap.caseInsensitiveMultiMap().addAll(proxiedRequest.headers()); - this.absoluteURI = proxiedRequest.absoluteURI(); - this.proxiedRequest = proxiedRequest; - this.context = ((HttpServerRequestInternal) proxiedRequest).context(); - this.authority = null; // null is used as a signal to indicate an unchanged authority - this.HOP_BY_HOP_HEADERS = new HashSet<>(customHopHeaders); - } - @Override public HttpVersion version() { return version; @@ -258,4 +229,16 @@ public Future send(HttpClientRequest request) { this.request = request; return sendRequest(); } + + @Override + public ProxyRequest setCustomHopHeaders(Set customHopHeaders) { + this.HOP_BY_HOP_HEADERS = new HashSet<>(customHopHeaders); + return this; + } + + @Override + public ProxyRequest addCustomHopHeader(String customHopHeader) { + this.HOP_BY_HOP_HEADERS.add(customHopHeader); + return this; + } } diff --git a/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java b/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java index cbffa37..eb8e31e 100644 --- a/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java +++ b/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java @@ -24,9 +24,7 @@ import io.vertx.httpproxy.*; import io.vertx.httpproxy.cache.CacheOptions; import io.vertx.httpproxy.spi.cache.Cache; - import java.util.*; - import static io.vertx.core.http.HttpHeaders.CONNECTION; import static io.vertx.core.http.HttpHeaders.HOST; import static io.vertx.core.http.HttpHeaders.UPGRADE; @@ -38,7 +36,7 @@ public class ReverseProxy implements HttpProxy { private final boolean supportWebSocket; private OriginRequestProvider originRequestProvider = (pc) -> Future.failedFuture("No origin available"); private final List interceptors = new ArrayList<>(); - private final List hopByHopHeaders; + private final Set hopByHopHeaders; public ReverseProxy(ProxyOptions options, HttpClient client) { CacheOptions cacheOptions = options.getCacheOptions(); @@ -75,7 +73,9 @@ public HttpProxy addInterceptor(ProxyInterceptor interceptor, boolean supportsWe @Override public void handle(HttpServerRequest request) { - ProxyRequest proxyRequest = ProxyRequest.reverseProxy(request, hopByHopHeaders); + ProxyRequest proxyRequest = ProxyRequest.reverseProxy(request); + + proxyRequest.setCustomHopHeaders(hopByHopHeaders); // Encoding sanity check Boolean chunked = HttpUtils.isChunked(request.headers());