diff --git a/src/main/java/io/vertx/httpproxy/ProxyOptions.java b/src/main/java/io/vertx/httpproxy/ProxyOptions.java index bae6bed..f1b8683 100644 --- a/src/main/java/io/vertx/httpproxy/ProxyOptions.java +++ b/src/main/java/io/vertx/httpproxy/ProxyOptions.java @@ -4,9 +4,20 @@ import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.json.JsonObject; import io.vertx.httpproxy.cache.CacheOptions; +import java.util.Arrays; +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) @@ -17,8 +28,20 @@ public class ProxyOptions { */ public static final boolean DEFAULT_SUPPORT_WEBSOCKET = true; + public static final Set DEFAULT_HOP_BY_HOP_HEADERS = new HashSet<>(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 Set customHopHeaders; public ProxyOptions(JsonObject json) { ProxyOptionsConverter.fromJson(json, this); @@ -26,6 +49,7 @@ public ProxyOptions(JsonObject json) { public ProxyOptions() { supportWebSocket = DEFAULT_SUPPORT_WEBSOCKET; + customHopHeaders = DEFAULT_HOP_BY_HOP_HEADERS; } /** @@ -66,6 +90,42 @@ public ProxyOptions setSupportWebSocket(boolean supportWebSocket) { return this; } + /** + * @return custom hop-by-hop headers + */ + public Set 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 + * @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 addCustomHopHeader(String customHopHeader) { + this.customHopHeaders.add(customHopHeader); + 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..3c20351 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.Set; /** * @@ -178,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 29926f5..b9c40a9 100644 --- a/src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java +++ b/src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java @@ -23,35 +23,22 @@ 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 static io.vertx.core.http.HttpHeaders.CONNECTION; +import java.util.HashSet; +import java.util.Set; 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 Set DEFAULT_HOP_BY_HOP_HEADERS = new HashSet<>(ProxyOptions.DEFAULT_HOP_BY_HOP_HEADERS); + private Set HOP_BY_HOP_HEADERS; final ContextInternal context; private HttpMethod method; private final HttpVersion version; @@ -88,6 +75,7 @@ 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; } @Override @@ -173,7 +161,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); } } @@ -241,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 cda186f..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,6 +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 Set hopByHopHeaders; public ReverseProxy(ProxyOptions options, HttpClient client) { CacheOptions cacheOptions = options.getCacheOptions(); @@ -47,6 +46,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) { @@ -75,6 +75,8 @@ public HttpProxy addInterceptor(ProxyInterceptor interceptor, boolean supportsWe public void handle(HttpServerRequest request) { ProxyRequest proxyRequest = ProxyRequest.reverseProxy(request); + proxyRequest.setCustomHopHeaders(hopByHopHeaders); + // Encoding sanity check Boolean chunked = HttpUtils.isChunked(request.headers()); if (chunked == null) {