diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc index f8baec3..2372992 100644 --- a/src/main/asciidoc/index.adoc +++ b/src/main/asciidoc/index.adoc @@ -143,27 +143,41 @@ You can change the control, e.g you can send a response immediately to the user- {@link examples.HttpProxyExamples#immediateResponse} ---- -==== Header interceptor +==== Head interceptor -You can apply header interceptors to change headers from the request and response with common operations: +You can use the {@link io.vertx.httpproxy.interceptors.HeadInterceptor} to change HTTP request/response heads: + +- request path +- query params +- request and response headers + +A {@link io.vertx.httpproxy.interceptors.HeadInterceptor} is created and configured with a {@link io.vertx.httpproxy.interceptors.HeadInterceptorBuilder}. + +The builder methods can be invoked several times. +Operations on the path will be invoked in the order of configuration. +That goes for operations on query parameters, request headers and response headers. + +===== Headers interception + +You can apply the interceptor to change headers from the request and response with common operations: [source,java] ---- {@link examples.HttpProxyExamples#headerInterceptorFilter} ---- -Check out the {@link io.vertx.httpproxy.interceptors.HeadersInterceptor} class for details about the available methods. +Check out {@link io.vertx.httpproxy.interceptors.HeadInterceptorBuilder} for details about the available methods. -==== Query interceptor +===== Query params interception -You can use query interceptors to update or remove the query parameters: +You can apply the interceptor to update or remove query parameters: [source,java] ---- {@link examples.HttpProxyExamples#queryInterceptorAdd} ---- -You can also refer to {@link io.vertx.httpproxy.interceptors.QueryInterceptor} for more information. +You can also refer to {@link io.vertx.httpproxy.interceptors.HeadInterceptorBuilder} for more information. ==== Body interceptor diff --git a/src/main/java/examples/HttpProxyExamples.java b/src/main/java/examples/HttpProxyExamples.java index ecd3042..ccc7646 100644 --- a/src/main/java/examples/HttpProxyExamples.java +++ b/src/main/java/examples/HttpProxyExamples.java @@ -109,12 +109,12 @@ public Future handleProxyResponse(ProxyContext context) { public void headerInterceptorFilter(HttpProxy proxy, Set shouldRemove) { // remove a set of headers proxy.addInterceptor( - HeadersInterceptor.filterResponseHeaders(shouldRemove)); + HeadInterceptor.builder().filteringResponseHeaders(shouldRemove).build()); } public void queryInterceptorAdd(HttpProxy proxy, String key, String value) { proxy.addInterceptor( - QueryInterceptor.setQueryParam(key, value)); + HeadInterceptor.builder().settingQueryParam(key, value).build()); } public void bodyInterceptorJson(HttpProxy proxy) { @@ -128,7 +128,7 @@ public void bodyInterceptorJson(HttpProxy proxy) { public void webSocketInterceptorPath(HttpProxy proxy) { proxy.addInterceptor( - WebSocketInterceptor.allow(PathInterceptor.addPrefix("/api")) + WebSocketInterceptor.allow(HeadInterceptor.builder().addingPathPrefix("/api").build()) ); } diff --git a/src/main/java/io/vertx/httpproxy/interceptors/HeadInterceptor.java b/src/main/java/io/vertx/httpproxy/interceptors/HeadInterceptor.java new file mode 100644 index 0000000..c97c68d --- /dev/null +++ b/src/main/java/io/vertx/httpproxy/interceptors/HeadInterceptor.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2011-2024 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 + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.httpproxy.interceptors; + +import io.vertx.codegen.annotations.Unstable; +import io.vertx.codegen.annotations.VertxGen; +import io.vertx.httpproxy.ProxyInterceptor; +import io.vertx.httpproxy.interceptors.impl.HeadInterceptorBuilderImpl; + +/** + * An interceptor updating HTTP request/response head attributes (headers, path, query params). + */ +@VertxGen +@Unstable +public interface HeadInterceptor extends ProxyInterceptor { + + /** + * @return a builder for head interception + */ + static HeadInterceptorBuilder builder() { + return new HeadInterceptorBuilderImpl(); + } +} diff --git a/src/main/java/io/vertx/httpproxy/interceptors/HeadInterceptorBuilder.java b/src/main/java/io/vertx/httpproxy/interceptors/HeadInterceptorBuilder.java new file mode 100644 index 0000000..f76196d --- /dev/null +++ b/src/main/java/io/vertx/httpproxy/interceptors/HeadInterceptorBuilder.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2011-2024 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 + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ + +package io.vertx.httpproxy.interceptors; + +import io.vertx.codegen.annotations.Fluent; +import io.vertx.codegen.annotations.GenIgnore; +import io.vertx.codegen.annotations.Unstable; +import io.vertx.codegen.annotations.VertxGen; +import io.vertx.core.Handler; +import io.vertx.core.MultiMap; + +import java.util.Set; +import java.util.function.Function; + +import static io.vertx.codegen.annotations.GenIgnore.PERMITTED_TYPE; + +/** + * Configuration for an interceptor updating HTTP request/response head attributes (headers, path, query params). + *

+ * All configuration methods can be invoked several times. + * Operations on the path will be invoked in the order of configuration. + * That goes for operations on request headers, response headers and query parameters. + */ +@VertxGen +@Unstable +public interface HeadInterceptorBuilder { + + /** + * @return an interceptor build according to builder requirements + */ + HeadInterceptor build(); + + /** + * Apply modifications to the query parameters. + * + * @param updater the operation to apply to the request query parameters (can be null, but in this case nothing happens) + * @return a reference to this, so the API can be used fluently + */ + @Fluent + HeadInterceptorBuilder updatingQueryParams(Handler updater); + + /** + * Add a query parameter to the request. + * + * @param name the parameter name (can be null, but in this case nothing happens) + * @param value the parameter value (can be null, but in this case nothing happens) + * @return a reference to this, so the API can be used fluently + */ + @Fluent + HeadInterceptorBuilder settingQueryParam(String name, String value); + + /** + * Remove a query parameter from the request. + * + * @param name the parameter name (can be null, but in this case nothing happens) + * @return a reference to this, so the API can be used fluently + */ + @Fluent + HeadInterceptorBuilder removingQueryParam(String name); + + /** + * Apply a callback to change the request URI when the proxy receives it. + * + * @param mutator the operation that applied to the path (can be null, but in this case nothing happens) + * @return a reference to this, so the API can be used fluently + */ + @Fluent + HeadInterceptorBuilder updatingPath(Function mutator); + + /** + * Add a prefix to the URI. + * + * @param prefix the prefix that need to be added (can be null, but in this case nothing happens) + * @return a reference to this, so the API can be used fluently + */ + @Fluent + HeadInterceptorBuilder addingPathPrefix(String prefix); + + /** + * Remove a prefix to the URI. Do nothing if it doesn't exist. + * + * @param prefix the prefix that need to be removed (can be null, but in this case nothing happens) + * @return a reference to this, so the API can be used fluently + */ + @Fluent + HeadInterceptorBuilder removingPathPrefix(String prefix); + + /** + * Apply callbacks to change the request headers when the proxy receives them. + * + * @param requestHeadersUpdater the operation to apply to the request headers (can be null, but in this case nothing happens) + * @return a reference to this, so the API can be used fluently + */ + @Fluent + HeadInterceptorBuilder updatingRequestHeaders(Handler requestHeadersUpdater); + + /** + * Apply callbacks to change the response headers when the proxy receives them. + * + * @param responseHeadersUpdater the operation to apply to the response headers (can be null, but in this case nothing happens) + * @return a reference to this, so the API can be used fluently + */ + @Fluent + HeadInterceptorBuilder updatingResponseHeaders(Handler responseHeadersUpdater); + + /** + * Filter the request headers in the given set. + * + * @param forbiddenRequestHeaders a set of the headers that need to be filtered (can be null, but in this case nothing happens) + * @return a reference to this, so the API can be used fluently + */ + @GenIgnore(PERMITTED_TYPE) + @Fluent + HeadInterceptorBuilder filteringRequestHeaders(Set forbiddenRequestHeaders); + + /** + * Filter the response headers in the given set. + * + * @param forbiddenResponseHeaders a set of the headers that need to be filtered (can be null, but in this case nothing happens) + * @return a reference to this, so the API can be used fluently + */ + @GenIgnore(PERMITTED_TYPE) + @Fluent + HeadInterceptorBuilder filteringResponseHeaders(Set forbiddenResponseHeaders); +} diff --git a/src/main/java/io/vertx/httpproxy/interceptors/HeadersInterceptor.java b/src/main/java/io/vertx/httpproxy/interceptors/HeadersInterceptor.java deleted file mode 100644 index cd49a20..0000000 --- a/src/main/java/io/vertx/httpproxy/interceptors/HeadersInterceptor.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2011-2024 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 - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.httpproxy.interceptors; - -import io.vertx.codegen.annotations.GenIgnore; -import io.vertx.codegen.annotations.Unstable; -import io.vertx.codegen.annotations.VertxGen; -import io.vertx.core.Handler; -import io.vertx.core.MultiMap; -import io.vertx.httpproxy.ProxyInterceptor; -import io.vertx.httpproxy.interceptors.impl.HeadersInterceptorImpl; - -import java.util.Set; - -import static io.vertx.codegen.annotations.GenIgnore.PERMITTED_TYPE; - -/** - * Used to create interceptors to modify request and response headers. - */ -@VertxGen -@Unstable -public interface HeadersInterceptor { - - /** - * Apply callbacks to change the request and response headers when the proxy receives them. - * - * @param changeRequestHeaders the operation to apply to the request headers - * @param changeResponseHeaders the operation to apply to the response headers - * @return the created interceptor - */ - static ProxyInterceptor changeHeaders(Handler changeRequestHeaders, Handler changeResponseHeaders) { - return new HeadersInterceptorImpl(changeRequestHeaders, changeResponseHeaders); - } - - /** - * Filter the request headers in the given set. - * - * @param requestHeaders a set of the headers that need to be filtered - * @return the created interceptor - */ - @GenIgnore(PERMITTED_TYPE) - static ProxyInterceptor filterRequestHeaders(Set requestHeaders) { - return HeadersInterceptorImpl.filter(requestHeaders, null); - } - - /** - * Filter the response headers in the given set. - * - * @param responseHeaders a set of the headers that need to be filtered - * @return the created interceptor - */ - @GenIgnore(PERMITTED_TYPE) - static ProxyInterceptor filterResponseHeaders(Set responseHeaders) { - return HeadersInterceptorImpl.filter(null, responseHeaders); - } - - /** - * Filter the request and response headers in the given sets. - * - * @param requestHeaders a set of the request headers that need to be filtered - * @param responseHeaders a set of the response headers that need to be filtered - * @return the created interceptor - */ - @GenIgnore(PERMITTED_TYPE) - static ProxyInterceptor filterHeaders(Set requestHeaders, Set responseHeaders) { - return HeadersInterceptorImpl.filter(requestHeaders, responseHeaders); - } -} diff --git a/src/main/java/io/vertx/httpproxy/interceptors/PathInterceptor.java b/src/main/java/io/vertx/httpproxy/interceptors/PathInterceptor.java deleted file mode 100644 index fd455fd..0000000 --- a/src/main/java/io/vertx/httpproxy/interceptors/PathInterceptor.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2011-2024 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 - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.httpproxy.interceptors; - -import io.vertx.codegen.annotations.Unstable; -import io.vertx.codegen.annotations.VertxGen; -import io.vertx.httpproxy.ProxyInterceptor; -import io.vertx.httpproxy.interceptors.impl.PathInterceptorImpl; - -import java.util.function.Function; - -/** - * Used to create interceptors to modify the request path. - */ -@VertxGen -@Unstable -public interface PathInterceptor { - - /** - * Apply a callback to change the request URI when the proxy receives it. - * - * @param pattern the operation that applied to the path - * @return the created interceptor - */ - static ProxyInterceptor changePath(Function pattern) { - return new PathInterceptorImpl(pattern); - } - - /** - * Add a prefix to the URI. - * - * @param prefix the prefix that need to be added - * @return the created interceptor - */ - static ProxyInterceptor removePrefix(String prefix) { - return PathInterceptorImpl.removePrefix(prefix); - } - - /** - * Remove a prefix to the URI. Do nothing if it doesn't exist. - * - * @param prefix the prefix that need to be removed - * @return the created interceptor - */ - static ProxyInterceptor addPrefix(String prefix) { - return PathInterceptorImpl.addPrefix(prefix); - } -} diff --git a/src/main/java/io/vertx/httpproxy/interceptors/QueryInterceptor.java b/src/main/java/io/vertx/httpproxy/interceptors/QueryInterceptor.java deleted file mode 100644 index 5c2ce8b..0000000 --- a/src/main/java/io/vertx/httpproxy/interceptors/QueryInterceptor.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2011-2024 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 - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.httpproxy.interceptors; - -import io.vertx.codegen.annotations.Unstable; -import io.vertx.codegen.annotations.VertxGen; -import io.vertx.core.Handler; -import io.vertx.core.MultiMap; -import io.vertx.httpproxy.ProxyInterceptor; -import io.vertx.httpproxy.interceptors.impl.QueryInterceptorImpl; - - -/** - * The general interceptor for query parameters. - */ -@VertxGen -@Unstable -public interface QueryInterceptor { - - /** - * Apply callbacks to modify the query parameters. - * - * @param changeQueries the operation to apply to the request query parameters - * @return the created interceptor - */ - static ProxyInterceptor changeQueryParams(Handler changeQueries) { - return new QueryInterceptorImpl(changeQueries); - } - - /** - * Add a query parameter to the request. - * - * @param name the parameter name - * @param value the parameter value - * @return the created interceptor - */ - static ProxyInterceptor setQueryParam(String name, String value) { - return new QueryInterceptorImpl(mmap -> mmap.set(name, value)); - } - - /** - * Remove a query parameter to the request. - * - * @param name the parameter name - * @return the created interceptor - */ - static ProxyInterceptor removeQueryParam(String name) { - return new QueryInterceptorImpl(mmap -> mmap.remove(name)); - } -} diff --git a/src/main/java/io/vertx/httpproxy/interceptors/impl/HeadInterceptorBuilderImpl.java b/src/main/java/io/vertx/httpproxy/interceptors/impl/HeadInterceptorBuilderImpl.java new file mode 100644 index 0000000..bbce2eb --- /dev/null +++ b/src/main/java/io/vertx/httpproxy/interceptors/impl/HeadInterceptorBuilderImpl.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2011-2024 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 + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ + +package io.vertx.httpproxy.interceptors.impl; + +import io.vertx.core.Handler; +import io.vertx.core.MultiMap; +import io.vertx.httpproxy.interceptors.HeadInterceptor; +import io.vertx.httpproxy.interceptors.HeadInterceptorBuilder; + +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toUnmodifiableList; + +public class HeadInterceptorBuilderImpl implements HeadInterceptorBuilder { + + // Fine to use stream builders here, since interceptors are typically configured on application startup and never modified + private final Stream.Builder> queryUpdaters = Stream.builder(); + private final Stream.Builder> pathUpdaters = Stream.builder(); + private final Stream.Builder> requestHeadersUpdaters = Stream.builder(); + private final Stream.Builder> responseHeadersUpdaters = Stream.builder(); + + @Override + public HeadInterceptor build() { + return new HeadInterceptorImpl( + queryUpdaters.build().collect(toUnmodifiableList()), + pathUpdaters.build().collect(toUnmodifiableList()), + requestHeadersUpdaters.build().collect(toUnmodifiableList()), + responseHeadersUpdaters.build().collect(toUnmodifiableList()) + ); + } + + @Override + public HeadInterceptorBuilder updatingQueryParams(Handler updater) { + if (updater != null) { + queryUpdaters.add(updater); + } + return this; + } + + @Override + public HeadInterceptorBuilder settingQueryParam(String name, String value) { + if (name != null && value != null) { + return updatingQueryParams(map -> map.set(name, value)); + } + return this; + } + + @Override + public HeadInterceptorBuilder removingQueryParam(String name) { + if (name != null) { + return updatingQueryParams(map -> map.remove(name)); + } + return this; + } + + @Override + public HeadInterceptorBuilder updatingPath(Function mutator) { + if (mutator != null) { + pathUpdaters.add(mutator); + } + return this; + } + + @Override + public HeadInterceptorBuilder addingPathPrefix(String prefix) { + if (prefix != null) { + return updatingPath(path -> prefix + path); + } + return this; + } + + @Override + public HeadInterceptorBuilder removingPathPrefix(String prefix) { + if (prefix != null) { + return updatingPath(path -> { + return path.startsWith(prefix) ? path.substring(prefix.length()) : path; + }); + } + return this; + } + + @Override + public HeadInterceptorBuilder updatingRequestHeaders(Handler requestHeadersUpdater) { + if (requestHeadersUpdater != null) { + requestHeadersUpdaters.add(requestHeadersUpdater); + } + return this; + } + + @Override + public HeadInterceptorBuilder updatingResponseHeaders(Handler responseHeadersUpdater) { + if (responseHeadersUpdater != null) { + responseHeadersUpdaters.add(responseHeadersUpdater); + } + return this; + } + + @Override + public HeadInterceptorBuilder filteringRequestHeaders(Set forbiddenRequestHeaders) { + if (forbiddenRequestHeaders != null) { + return updatingRequestHeaders(headers -> { + for (CharSequence cs : forbiddenRequestHeaders) { + headers.remove(cs); + } + }); + } + return this; + } + + @Override + public HeadInterceptorBuilder filteringResponseHeaders(Set forbiddenResponseHeaders) { + if (forbiddenResponseHeaders != null) { + return updatingResponseHeaders(headers -> { + for (CharSequence cs : forbiddenResponseHeaders) { + headers.remove(cs); + } + }); + } + return this; + } +} diff --git a/src/main/java/io/vertx/httpproxy/interceptors/impl/QueryInterceptorImpl.java b/src/main/java/io/vertx/httpproxy/interceptors/impl/HeadInterceptorImpl.java similarity index 53% rename from src/main/java/io/vertx/httpproxy/interceptors/impl/QueryInterceptorImpl.java rename to src/main/java/io/vertx/httpproxy/interceptors/impl/HeadInterceptorImpl.java index 6b03c76..f7c877a 100644 --- a/src/main/java/io/vertx/httpproxy/interceptors/impl/QueryInterceptorImpl.java +++ b/src/main/java/io/vertx/httpproxy/interceptors/impl/HeadInterceptorImpl.java @@ -17,28 +17,53 @@ import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.httpproxy.ProxyContext; -import io.vertx.httpproxy.ProxyInterceptor; +import io.vertx.httpproxy.ProxyRequest; import io.vertx.httpproxy.ProxyResponse; +import io.vertx.httpproxy.interceptors.HeadInterceptor; +import java.util.List; import java.util.Objects; +import java.util.function.Function; -public class QueryInterceptorImpl implements ProxyInterceptor { - private final Handler changeQueryParams; +class HeadInterceptorImpl implements HeadInterceptor { + + private final List> queryUpdaters; + private final List> pathUpdaters; + private final List> requestHeadersUpdaters; + private final List> responseHeadersUpdaters; + + HeadInterceptorImpl(List> queryUpdaters, List> pathUpdaters, List> requestHeadersUpdaters, List> responseHeadersUpdaters) { + this.queryUpdaters = Objects.requireNonNull(queryUpdaters); + this.pathUpdaters = Objects.requireNonNull(pathUpdaters); + this.requestHeadersUpdaters = Objects.requireNonNull(requestHeadersUpdaters); + this.responseHeadersUpdaters = Objects.requireNonNull(responseHeadersUpdaters); + } @Override public Future handleProxyRequest(ProxyContext context) { + queryHandleProxyRequest(context); + pathHandleProxyRequest(context); + headersHandleProxyRequest(context); + return context.sendRequest(); + } + + @Override + public Future handleProxyResponse(ProxyContext context) { + headersHandleProxyResponse(context); + return context.sendResponse(); + } + + private void queryHandleProxyRequest(ProxyContext context) { String rawUri = context.request().getURI(); MultiMap params = queryParams(rawUri); String cleanedUri = cleanedUri(rawUri); - changeQueryParams.handle(params); + for (Handler queryUpdater : queryUpdaters) { + queryUpdater.handle(params); + } + String newUri = buildUri(cleanedUri, params); context.request().setURI(newUri); - return context.sendRequest(); - } - - public QueryInterceptorImpl(Handler changeQueryParams) { - this.changeQueryParams = Objects.requireNonNull(changeQueryParams); } // ref: https://github.com/vert-x3/vertx-web/blob/master/vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/HttpRequestImpl.java @@ -76,4 +101,25 @@ private static String buildUri(String uri, MultiMap queryParams) { uri = encoder.toString(); return uri; } + + private void pathHandleProxyRequest(ProxyContext context) { + ProxyRequest proxyRequest = context.request(); + for (Function pathUpdater : pathUpdaters) { + proxyRequest.setURI(pathUpdater.apply(proxyRequest.getURI())); + } + } + + private void headersHandleProxyRequest(ProxyContext context) { + ProxyRequest request = context.request(); + for (Handler requestHeadersUpdater : requestHeadersUpdaters) { + requestHeadersUpdater.handle(request.headers()); + } + } + + private void headersHandleProxyResponse(ProxyContext context) { + ProxyResponse response = context.response(); + for (Handler responseHeadersUpdater : responseHeadersUpdaters) { + responseHeadersUpdater.handle(response.headers()); + } + } } diff --git a/src/main/java/io/vertx/httpproxy/interceptors/impl/HeadersInterceptorImpl.java b/src/main/java/io/vertx/httpproxy/interceptors/impl/HeadersInterceptorImpl.java deleted file mode 100644 index 86d59bc..0000000 --- a/src/main/java/io/vertx/httpproxy/interceptors/impl/HeadersInterceptorImpl.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2011-2024 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 - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.httpproxy.interceptors.impl; - -import io.vertx.core.Future; -import io.vertx.core.Handler; -import io.vertx.core.MultiMap; -import io.vertx.httpproxy.ProxyContext; -import io.vertx.httpproxy.ProxyInterceptor; -import io.vertx.httpproxy.ProxyRequest; -import io.vertx.httpproxy.ProxyResponse; - -import java.util.Objects; -import java.util.Set; - -/** - * The general interceptor for headers. - */ -public class HeadersInterceptorImpl implements ProxyInterceptor { - - private final Handler changeRequestHeaders; - private final Handler changeResponseHeaders; - - private static final Handler NO_OP = mmap -> {}; - - public HeadersInterceptorImpl(Handler changeRequestHeaders, Handler changeResponseHeaders) { - this.changeRequestHeaders = Objects.requireNonNull(changeRequestHeaders); - this.changeResponseHeaders = Objects.requireNonNull(changeResponseHeaders); - } - - public static HeadersInterceptorImpl filter(Set requestHeaders, Set responseHeaders) { - return new HeadersInterceptorImpl( - requestHeaders == null || requestHeaders.isEmpty() ? - NO_OP : oldRequestHeader -> requestHeaders.forEach(oldRequestHeader::remove), - responseHeaders == null || responseHeaders.isEmpty() ? - NO_OP : oldResponseHeader -> responseHeaders.forEach(oldResponseHeader::remove) - ); - } - - @Override - public Future handleProxyRequest(ProxyContext context) { - ProxyRequest request = context.request(); - changeRequestHeaders.handle(request.headers()); - return context.sendRequest(); - } - - @Override - public Future handleProxyResponse(ProxyContext context) { - ProxyResponse response = context.response(); - changeResponseHeaders.handle(response.headers()); - return context.sendResponse(); - } -} diff --git a/src/main/java/io/vertx/httpproxy/interceptors/impl/PathInterceptorImpl.java b/src/main/java/io/vertx/httpproxy/interceptors/impl/PathInterceptorImpl.java deleted file mode 100644 index 382e45b..0000000 --- a/src/main/java/io/vertx/httpproxy/interceptors/impl/PathInterceptorImpl.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2011-2024 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 - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.httpproxy.interceptors.impl; - -import io.vertx.core.Future; -import io.vertx.httpproxy.ProxyContext; -import io.vertx.httpproxy.ProxyInterceptor; -import io.vertx.httpproxy.ProxyRequest; -import io.vertx.httpproxy.ProxyResponse; - -import java.util.Objects; -import java.util.function.Function; - -/** - * The general interceptor for path. - */ -public class PathInterceptorImpl implements ProxyInterceptor { - private final Function pattern; - - public PathInterceptorImpl(Function pattern) { - this.pattern = Objects.requireNonNull(pattern); - } - - public static PathInterceptorImpl addPrefix(String prefix) { - return new PathInterceptorImpl(uri -> prefix + uri); - } - - public static PathInterceptorImpl removePrefix(String prefix) { - return new PathInterceptorImpl(uri -> uri.startsWith(prefix) ? uri.substring(prefix.length()) : uri); - } - - @Override - public Future handleProxyRequest(ProxyContext context) { - ProxyRequest proxyRequest = context.request(); - proxyRequest.setURI(pattern.apply(proxyRequest.getURI())); - return context.sendRequest(); - } -} diff --git a/src/test/java/io/vertx/tests/interceptors/HeadInterceptorTest.java b/src/test/java/io/vertx/tests/interceptors/HeadInterceptorTest.java new file mode 100644 index 0000000..debbaee --- /dev/null +++ b/src/test/java/io/vertx/tests/interceptors/HeadInterceptorTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2011-2024 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 + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ + +package io.vertx.tests.interceptors; + +import io.netty.handler.codec.http.QueryStringDecoder; +import io.vertx.core.MultiMap; +import io.vertx.core.http.HttpClientRequest; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.net.SocketAddress; +import io.vertx.ext.unit.Async; +import io.vertx.ext.unit.TestContext; +import io.vertx.httpproxy.ProxyOptions; +import io.vertx.httpproxy.interceptors.HeadInterceptor; +import io.vertx.tests.ProxyTestBase; +import org.junit.Test; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class HeadInterceptorTest extends ProxyTestBase { + + public HeadInterceptorTest(ProxyOptions options) { + super(options); + } + + @Test + public void testCombined(TestContext ctx) { + Async latch = ctx.async(); + SocketAddress backend = startHttpBackend(ctx, 8081, req -> { + ctx.assertEquals(req.path(), "/prefix/hello"); + QueryStringDecoder decoder = new QueryStringDecoder(req.uri()); + Map> parameters = decoder.parameters(); + ctx.assertEquals(parameters.get("k1").get(0), "v1"); + ctx.assertEquals(parameters.get("k2").get(0), "v2"); + MultiMap headers = req.headers(); + ctx.assertEquals(headers.get("k1"), "v2"); + ctx.assertEquals(headers.get("k2"), null); + req.response().end("Hello"); + }); + + startProxy(proxy -> proxy.origin(backend) + .addInterceptor(HeadInterceptor.builder() + .settingQueryParam("k1", "v1") + .addingPathPrefix("/prefix") + .filteringRequestHeaders(Set.of("k2")) + .updatingRequestHeaders(headers -> { + ctx.assertNull(headers.get("k2")); + headers.set("k1", "v2"); + }) + .build())); + + vertx.createHttpClient().request(HttpMethod.GET, 8080, "localhost", "/hello?k2=v2") + .compose(HttpClientRequest::send) + .onComplete(ctx.asyncAssertSuccess(resp -> latch.complete())); + } +} diff --git a/src/test/java/io/vertx/tests/interceptors/HeaderInterceptorTest.java b/src/test/java/io/vertx/tests/interceptors/HeaderInterceptorTest.java index 26d66a5..0c80939 100644 --- a/src/test/java/io/vertx/tests/interceptors/HeaderInterceptorTest.java +++ b/src/test/java/io/vertx/tests/interceptors/HeaderInterceptorTest.java @@ -17,7 +17,7 @@ import io.vertx.ext.unit.Async; import io.vertx.ext.unit.TestContext; import io.vertx.httpproxy.ProxyOptions; -import io.vertx.httpproxy.interceptors.HeadersInterceptor; +import io.vertx.httpproxy.interceptors.HeadInterceptor; import io.vertx.tests.ProxyTestBase; import org.junit.Test; @@ -42,7 +42,7 @@ public void testFilterRequestHeader(TestContext ctx) { }); startProxy(proxy -> proxy.origin(backend) - .addInterceptor(HeadersInterceptor.filterRequestHeaders(Set.of("k2")))); + .addInterceptor(HeadInterceptor.builder().filteringRequestHeaders(Set.of("k2")).build())); vertx.createHttpClient().request(HttpMethod.GET, 8080, "localhost", "/") .compose(request -> request @@ -65,7 +65,7 @@ public void testFilterResponseHeader(TestContext ctx) { }); startProxy(proxy -> proxy.origin(backend) - .addInterceptor(HeadersInterceptor.filterResponseHeaders(Set.of("k2")))); + .addInterceptor(HeadInterceptor.builder().filteringResponseHeaders(Set.of("k2")).build())); vertx.createHttpClient().request(HttpMethod.GET, 8080, "localhost", "/") .compose(HttpClientRequest::send) diff --git a/src/test/java/io/vertx/tests/interceptors/PathInterceptorTest.java b/src/test/java/io/vertx/tests/interceptors/PathInterceptorTest.java index d603a0a..91106de 100644 --- a/src/test/java/io/vertx/tests/interceptors/PathInterceptorTest.java +++ b/src/test/java/io/vertx/tests/interceptors/PathInterceptorTest.java @@ -17,7 +17,7 @@ import io.vertx.ext.unit.Async; import io.vertx.ext.unit.TestContext; import io.vertx.httpproxy.ProxyOptions; -import io.vertx.httpproxy.interceptors.PathInterceptor; +import io.vertx.httpproxy.interceptors.HeadInterceptor; import io.vertx.tests.ProxyTestBase; import org.junit.Test; @@ -39,7 +39,7 @@ public void addPrefixTest(TestContext ctx) { }); startProxy(proxy -> proxy.origin(backend) - .addInterceptor(PathInterceptor.addPrefix("/prefix"))); + .addInterceptor(HeadInterceptor.builder().addingPathPrefix("/prefix").build())); vertx.createHttpClient().request(HttpMethod.GET, 8080, "localhost", "/hello") .compose(HttpClientRequest::send) @@ -55,11 +55,26 @@ public void removePrefixTest(TestContext ctx) { }); startProxy(proxy -> proxy.origin(backend) - .addInterceptor(PathInterceptor.removePrefix("/prefix"))); + .addInterceptor(HeadInterceptor.builder().removingPathPrefix("/prefix").build())); vertx.createHttpClient().request(HttpMethod.GET, 8080, "localhost", "/prefix/hello") .compose(HttpClientRequest::send) .onComplete(ctx.asyncAssertSuccess(resp -> latch.complete())); } + @Test + public void absentPrefixTest(TestContext ctx) { + Async latch = ctx.async(); + SocketAddress backend = startHttpBackend(ctx, 8081, req -> { + ctx.assertEquals(req.uri(), "/hello"); + req.response().end("Hello"); + }); + + startProxy(proxy -> proxy.origin(backend) + .addInterceptor(HeadInterceptor.builder().removingPathPrefix("/prefix").build())); + + vertx.createHttpClient().request(HttpMethod.GET, 8080, "localhost", "/hello") + .compose(HttpClientRequest::send) + .onComplete(ctx.asyncAssertSuccess(resp -> latch.complete())); + } } diff --git a/src/test/java/io/vertx/tests/interceptors/QueryInterceptorTest.java b/src/test/java/io/vertx/tests/interceptors/QueryInterceptorTest.java index e18a1be..ab46412 100644 --- a/src/test/java/io/vertx/tests/interceptors/QueryInterceptorTest.java +++ b/src/test/java/io/vertx/tests/interceptors/QueryInterceptorTest.java @@ -18,7 +18,7 @@ import io.vertx.ext.unit.Async; import io.vertx.ext.unit.TestContext; import io.vertx.httpproxy.ProxyOptions; -import io.vertx.httpproxy.interceptors.QueryInterceptor; +import io.vertx.httpproxy.interceptors.HeadInterceptor; import io.vertx.tests.ProxyTestBase; import org.junit.Test; @@ -46,7 +46,7 @@ public void addParamTest(TestContext ctx) { }); startProxy(proxy -> proxy.origin(backend) - .addInterceptor(QueryInterceptor.setQueryParam("k1", "v1"))); + .addInterceptor(HeadInterceptor.builder().settingQueryParam("k1", "v1").build())); vertx.createHttpClient().request(HttpMethod.GET, 8080, "localhost", "/hello/world?k2=v2") .compose(HttpClientRequest::send) @@ -65,7 +65,7 @@ public void removeParamTest(TestContext ctx) { }); startProxy(proxy -> proxy.origin(backend) - .addInterceptor(QueryInterceptor.removeQueryParam("k2"))); + .addInterceptor(HeadInterceptor.builder().removingQueryParam("k2").build())); vertx.createHttpClient().request(HttpMethod.GET, 8080, "localhost", "/hello/world?k1=v1&k2=v2") .compose(HttpClientRequest::send) diff --git a/src/test/java/io/vertx/tests/interceptors/WebSocketInterceptorTest.java b/src/test/java/io/vertx/tests/interceptors/WebSocketInterceptorTest.java index ab7d194..01b865a 100644 --- a/src/test/java/io/vertx/tests/interceptors/WebSocketInterceptorTest.java +++ b/src/test/java/io/vertx/tests/interceptors/WebSocketInterceptorTest.java @@ -7,7 +7,7 @@ import io.vertx.ext.unit.Async; import io.vertx.ext.unit.TestContext; import io.vertx.httpproxy.*; -import io.vertx.httpproxy.interceptors.PathInterceptor; +import io.vertx.httpproxy.interceptors.HeadInterceptor; import io.vertx.httpproxy.interceptors.WebSocketInterceptor; import io.vertx.tests.ProxyTestBase; import org.junit.Test; @@ -86,14 +86,14 @@ public void testNotInterceptor(TestContext ctx) { @Test public void testNotApplySocket(TestContext ctx) { // this interceptor only applies to regular HTTP traffic - ProxyInterceptor interceptor = PathInterceptor.changePath(x -> x + "/updated"); + ProxyInterceptor interceptor = HeadInterceptor.builder().updatingPath(x -> x + "/updated").build(); testWithInterceptor(ctx, interceptor, true, false); } @Test public void testWithSocketInterceptor(TestContext ctx) { // this interceptor applies to both regular HTTP traffic and WebSocket handshake - ProxyInterceptor interceptor = WebSocketInterceptor.allow(PathInterceptor.changePath(x -> x + "/updated")); + ProxyInterceptor interceptor = WebSocketInterceptor.allow(HeadInterceptor.builder().updatingPath(x -> x + "/updated").build()); testWithInterceptor(ctx, interceptor, true, true); }