summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorten Tokle <mortent@verizonmedia.com>2020-11-17 15:05:24 +0100
committerGitHub <noreply@github.com>2020-11-17 15:05:24 +0100
commit5c9509b18fe54e28441e2c8397f05430a8124d6f (patch)
treea8e65ada7f88dce72c2a1e3ea1805375035fd3f4
parent76fe9fd501092f4f4fa350f3dc457a70f6cda539 (diff)
parent2e071a71b433a776ad4969abbd0b64a9e5ca0c65 (diff)
Merge pull request #15367 from vespa-engine/bjorncs/jdisc-http-service-filtering
Bjorncs/jdisc http service filtering
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java5
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/FilterBindingsProvider.java98
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/FilterUtil.java99
-rw-r--r--container-jersey2/pom.xml5
-rw-r--r--container-search-gui/pom.xml5
-rw-r--r--container-test-jars/jersey-resources/pom.xml5
-rw-r--r--jaxrs_utils/pom.xml5
-rw-r--r--jdisc_http_service/abi-spec.json36
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindings.java120
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterResolver.java56
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilteringRequestHandler.java16
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java2
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscContext.java4
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscFilterInvokerFilter.java13
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java3
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java50
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/MetricDefinitions.java6
-rw-r--r--jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.jdisc.http.server.def6
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/FilterTestCase.java177
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java13
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/MetricConsumerMock.java28
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/JDiscFilterForServletTest.java7
-rw-r--r--node-admin/pom.xml5
-rw-r--r--orchestrator-restapi/pom.xml5
24 files changed, 497 insertions, 272 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java
index 5c3fe59b3fd..21f323fc0f3 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java
@@ -215,6 +215,11 @@ public class VespaMetricSet {
addMetric(metrics, "jdisc.http.jetty.threadpool.thread.total", List.of("sum", "count", "min", "max"));
addMetric(metrics, "jdisc.http.jetty.threadpool.queue.size", List.of("sum", "count", "min", "max"));
+ addMetric(metrics, "jdisc.http.filtering.request.handled", List.of("rate"));
+ addMetric(metrics, "jdisc.http.filtering.request.unhandled", List.of("rate"));
+ addMetric(metrics, "jdisc.http.filtering.response.handled", List.of("rate"));
+ addMetric(metrics, "jdisc.http.filtering.response.unhandled", List.of("rate"));
+
return metrics;
}
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/FilterBindingsProvider.java b/container-disc/src/main/java/com/yahoo/container/jdisc/FilterBindingsProvider.java
index 195aee93246..6527a368113 100644
--- a/container-disc/src/main/java/com/yahoo/container/jdisc/FilterBindingsProvider.java
+++ b/container-disc/src/main/java/com/yahoo/container/jdisc/FilterBindingsProvider.java
@@ -1,16 +1,21 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.container.jdisc;
+import com.google.inject.Inject;
import com.yahoo.component.ComponentId;
+import com.yahoo.component.ComponentSpecification;
import com.yahoo.component.provider.ComponentRegistry;
import com.yahoo.container.di.componentgraph.Provider;
import com.yahoo.container.http.filter.FilterChainRepository;
import com.yahoo.jdisc.http.ServerConfig;
+import com.yahoo.jdisc.http.filter.RequestFilter;
+import com.yahoo.jdisc.http.filter.ResponseFilter;
import com.yahoo.jdisc.http.filter.SecurityRequestFilter;
+import com.yahoo.jdisc.http.filter.SecurityRequestFilterChain;
import com.yahoo.jdisc.http.server.jetty.FilterBindings;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
/**
* Provides filter bindings based on vespa config.
@@ -20,35 +25,100 @@ import java.util.List;
*/
public class FilterBindingsProvider implements Provider<FilterBindings> {
+ private static final ComponentId SEARCH_SERVER_COMPONENT_ID = ComponentId.fromString("SearchServer");
+
private final FilterBindings filterBindings;
+ @Inject
public FilterBindingsProvider(ComponentId componentId,
ServerConfig config,
FilterChainRepository filterChainRepository,
ComponentRegistry<SecurityRequestFilter> legacyRequestFilters) {
try {
- this.filterBindings = FilterUtil.setupFilters(
- componentId,
- legacyRequestFilters,
- toFilterSpecs(config.filter()),
- filterChainRepository);
+ FilterBindings.Builder builder = new FilterBindings.Builder();
+ configureLegacyFilters(builder, componentId, legacyRequestFilters);
+ configureFilters(builder, config, filterChainRepository);
+ this.filterBindings = builder.build();
} catch (Exception e) {
throw new RuntimeException(
"Invalid config for http server '" + componentId.getNamespace() + "': " + e.getMessage(), e);
}
}
- private List<FilterUtil.FilterSpec> toFilterSpecs(List<ServerConfig.Filter> inFilters) {
- List<FilterUtil.FilterSpec> outFilters = new ArrayList<>();
- for (ServerConfig.Filter inFilter : inFilters) {
- outFilters.add(new FilterUtil.FilterSpec(inFilter.id(), inFilter.binding()));
+ // TODO(gjoranv): remove
+ private static void configureLegacyFilters(
+ FilterBindings.Builder builder,
+ ComponentId id,
+ ComponentRegistry<SecurityRequestFilter> legacyRequestFilters) {
+ ComponentId serverName = id.getNamespace();
+ if (SEARCH_SERVER_COMPONENT_ID.equals(serverName) && !legacyRequestFilters.allComponents().isEmpty()) {
+ String filterId = "legacy-filters";
+ builder.addRequestFilter(filterId, SecurityRequestFilterChain.newInstance(legacyRequestFilters.allComponents()));
+ builder.addRequestFilterBinding(filterId, "http://*/*");
+ }
+ }
+
+ private static void configureFilters(
+ FilterBindings.Builder builder, ServerConfig config, FilterChainRepository filterRepository) {
+ addFilterInstances(builder, config, filterRepository);
+ addFilterBindings(builder, config, filterRepository);
+ addPortDefaultFilters(builder, config, filterRepository);
+ }
+
+ private static void addFilterInstances(
+ FilterBindings.Builder builder, ServerConfig config, FilterChainRepository filterRepository) {
+ Set<String> filterIds = new HashSet<>();
+ config.filter().forEach(filterBinding -> filterIds.add(filterBinding.id()));
+ config.defaultFilters().forEach(defaultFilter -> filterIds.add(defaultFilter.filterId()));
+ for (String filterId : filterIds) {
+ Object filterInstance = getFilterInstance(filterRepository, filterId);
+ if (filterInstance instanceof RequestFilter && filterInstance instanceof ResponseFilter) {
+ throw new IllegalArgumentException("The filter " + filterInstance.getClass().getName()
+ + " is unsupported since it's both a RequestFilter and a ResponseFilter.");
+ } else if (filterInstance instanceof RequestFilter) {
+ builder.addRequestFilter(filterId, (RequestFilter)filterInstance);
+ } else if (filterInstance instanceof ResponseFilter) {
+ builder.addResponseFilter(filterId, (ResponseFilter)filterInstance);
+ } else if (filterInstance == null) {
+ throw new IllegalArgumentException("No http filter with id " + filterId);
+ } else {
+ throw new IllegalArgumentException("Unknown filter type: " + filterInstance.getClass().getName());
+ }
+ }
+ }
+
+ private static void addFilterBindings(
+ FilterBindings.Builder builder, ServerConfig config, FilterChainRepository filterRepository) {
+ for (ServerConfig.Filter filterBinding : config.filter()) {
+ if (isRequestFilter(filterRepository, filterBinding.id())) {
+ builder.addRequestFilterBinding(filterBinding.id(), filterBinding.binding());
+ } else {
+ builder.addResponseFilterBinding(filterBinding.id(), filterBinding.binding());
+ }
+ }
+ }
+
+ private static void addPortDefaultFilters(
+ FilterBindings.Builder builder, ServerConfig config, FilterChainRepository filterRepository) {
+ for (ServerConfig.DefaultFilters defaultFilter : config.defaultFilters()) {
+ if (isRequestFilter(filterRepository, defaultFilter.filterId())) {
+ builder.setRequestFilterDefaultForPort(defaultFilter.filterId(), defaultFilter.localPort());
+ } else {
+ builder.setResponseFilterDefaultForPort(defaultFilter.filterId(), defaultFilter.localPort());
+ }
}
- return outFilters;
+ }
+
+ private static boolean isRequestFilter(FilterChainRepository filterRepository, String filterId) {
+ return getFilterInstance(filterRepository, filterId) instanceof RequestFilter;
+ }
+
+ private static Object getFilterInstance(FilterChainRepository filterRepository, String filterId) {
+ return filterRepository.getFilter(ComponentSpecification.fromString(filterId));
}
@Override public FilterBindings get() { return filterBindings; }
- @Override
- public void deconstruct() {}
+ @Override public void deconstruct() {}
}
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/FilterUtil.java b/container-disc/src/main/java/com/yahoo/container/jdisc/FilterUtil.java
deleted file mode 100644
index 52829d6710e..00000000000
--- a/container-disc/src/main/java/com/yahoo/container/jdisc/FilterUtil.java
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.jdisc;
-
-import com.yahoo.component.ComponentId;
-import com.yahoo.component.ComponentSpecification;
-import com.yahoo.component.provider.ComponentRegistry;
-import com.yahoo.container.http.filter.FilterChainRepository;
-import com.yahoo.jdisc.http.filter.RequestFilter;
-import com.yahoo.jdisc.http.filter.ResponseFilter;
-import com.yahoo.jdisc.http.filter.SecurityRequestFilter;
-import com.yahoo.jdisc.http.filter.SecurityRequestFilterChain;
-import com.yahoo.jdisc.http.server.jetty.FilterBindings;
-
-import java.util.List;
-
-/**
- * Helper class to set up filter binding repositories based on config.
- *
- * @author Øyvind Bakksjø
- * @author bjorncs
- */
-class FilterUtil {
-
- private static final ComponentId SEARCH_SERVER_COMPONENT_ID = ComponentId.fromString("SearchServer");
-
- private final FilterBindings.Builder builder = new FilterBindings.Builder();
-
- private FilterUtil() {}
-
- private void configureFilters(List<FilterSpec> filtersConfig, FilterChainRepository filterChainRepository) {
- for (FilterSpec filterConfig : filtersConfig) {
- Object filter = filterChainRepository.getFilter(ComponentSpecification.fromString(filterConfig.getId()));
- if (filter == null) {
- throw new RuntimeException("No http filter with id " + filterConfig.getId());
- }
- addFilter(filter, filterConfig.getBinding(), filterConfig.getId());
- }
- }
-
- private void addFilter(Object filter, String binding, String filterId) {
- if (filter instanceof RequestFilter && filter instanceof ResponseFilter) {
- throw new RuntimeException("The filter " + filter.getClass().getName() +
- " is unsupported since it's both a RequestFilter and a ResponseFilter.");
- } else if (filter instanceof RequestFilter) {
- builder.addRequestFilter(filterId, binding, (RequestFilter) filter);
- } else if (filter instanceof ResponseFilter) {
- builder.addResponseFilter(filterId, binding, (ResponseFilter) filter);
- } else {
- throw new RuntimeException("Unknown filter type " + filter.getClass().getName());
- }
- }
-
- // TODO(gjoranv): remove
- private void configureLegacyFilters(ComponentId id, ComponentRegistry<SecurityRequestFilter> legacyRequestFilters) {
- ComponentId serverName = id.getNamespace();
- if (SEARCH_SERVER_COMPONENT_ID.equals(serverName) && !legacyRequestFilters.allComponents().isEmpty()) {
- builder.addRequestFilter(
- "legacy-filters", "http://*/*", SecurityRequestFilterChain.newInstance(legacyRequestFilters.allComponents()));
- }
- }
-
- /**
- * Populates binding repositories with filters based on config.
- */
- public static FilterBindings setupFilters(
- ComponentId componentId,
- ComponentRegistry<SecurityRequestFilter> legacyRequestFilters,
- List<FilterSpec> filtersConfig,
- FilterChainRepository filterChainRepository) {
- FilterUtil filterUtil = new FilterUtil();
-
- // TODO(gjoranv): remove
- filterUtil.configureLegacyFilters(componentId, legacyRequestFilters);
-
- filterUtil.configureFilters(filtersConfig, filterChainRepository);
-
- return filterUtil.builder.build();
- }
-
- public static class FilterSpec {
-
- private final String id;
- private final String binding;
-
- public FilterSpec(String id, String binding) {
- this.id = id;
- this.binding = binding;
- }
-
- public String getId() {
- return id;
- }
-
- public String getBinding() {
- return binding;
- }
- }
-
-}
diff --git a/container-jersey2/pom.xml b/container-jersey2/pom.xml
index ee2c0f0cfab..231e9dd4a0e 100644
--- a/container-jersey2/pom.xml
+++ b/container-jersey2/pom.xml
@@ -71,6 +71,11 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>abi-check-plugin</artifactId>
</plugin>
+ <plugin>
+ <!-- Explicit for IntelliJ to detect correct language level from parent -->
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ </plugin>
</plugins>
</build>
</project>
diff --git a/container-search-gui/pom.xml b/container-search-gui/pom.xml
index c51ce8595af..867f2464b8b 100644
--- a/container-search-gui/pom.xml
+++ b/container-search-gui/pom.xml
@@ -85,6 +85,11 @@
<artifactId>bundle-plugin</artifactId>
<extensions>true</extensions>
</plugin>
+ <plugin>
+ <!-- Explicit for IntelliJ to detect correct language level from parent -->
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ </plugin>
</plugins>
</build>
diff --git a/container-test-jars/jersey-resources/pom.xml b/container-test-jars/jersey-resources/pom.xml
index 413f06784e8..cb35be91cfd 100644
--- a/container-test-jars/jersey-resources/pom.xml
+++ b/container-test-jars/jersey-resources/pom.xml
@@ -26,6 +26,11 @@
<build>
<plugins>
<plugin>
+ <!-- Explicit for IntelliJ to detect correct language level from parent -->
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ </plugin>
+ <plugin>
<groupId>com.yahoo.vespa</groupId>
<artifactId>bundle-plugin</artifactId>
<version>${project.version}</version>
diff --git a/jaxrs_utils/pom.xml b/jaxrs_utils/pom.xml
index a75182b0966..9ad85588daa 100644
--- a/jaxrs_utils/pom.xml
+++ b/jaxrs_utils/pom.xml
@@ -26,6 +26,11 @@
<build>
<plugins>
<plugin>
+ <!-- Explicit for IntelliJ to detect correct language level from parent -->
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ </plugin>
+ <plugin>
<groupId>com.yahoo.vespa</groupId>
<artifactId>bundle-plugin</artifactId>
<extensions>true</extensions>
diff --git a/jdisc_http_service/abi-spec.json b/jdisc_http_service/abi-spec.json
index c19ce5ce83c..8bf7f30964a 100644
--- a/jdisc_http_service/abi-spec.json
+++ b/jdisc_http_service/abi-spec.json
@@ -740,6 +740,8 @@
"public com.yahoo.jdisc.http.ServerConfig$Builder removeRawPostBodyForWwwUrlEncodedPost(boolean)",
"public com.yahoo.jdisc.http.ServerConfig$Builder filter(com.yahoo.jdisc.http.ServerConfig$Filter$Builder)",
"public com.yahoo.jdisc.http.ServerConfig$Builder filter(java.util.List)",
+ "public com.yahoo.jdisc.http.ServerConfig$Builder defaultFilters(com.yahoo.jdisc.http.ServerConfig$DefaultFilters$Builder)",
+ "public com.yahoo.jdisc.http.ServerConfig$Builder defaultFilters(java.util.List)",
"public com.yahoo.jdisc.http.ServerConfig$Builder maxWorkerThreads(int)",
"public com.yahoo.jdisc.http.ServerConfig$Builder minWorkerThreads(int)",
"public com.yahoo.jdisc.http.ServerConfig$Builder stopTimeout(double)",
@@ -754,11 +756,43 @@
],
"fields": [
"public java.util.List filter",
+ "public java.util.List defaultFilters",
"public com.yahoo.jdisc.http.ServerConfig$Jmx$Builder jmx",
"public com.yahoo.jdisc.http.ServerConfig$Metric$Builder metric",
"public com.yahoo.jdisc.http.ServerConfig$AccessLog$Builder accessLog"
]
},
+ "com.yahoo.jdisc.http.ServerConfig$DefaultFilters$Builder": {
+ "superClass": "java.lang.Object",
+ "interfaces": [
+ "com.yahoo.config.ConfigBuilder"
+ ],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>()",
+ "public void <init>(com.yahoo.jdisc.http.ServerConfig$DefaultFilters)",
+ "public com.yahoo.jdisc.http.ServerConfig$DefaultFilters$Builder filterId(java.lang.String)",
+ "public com.yahoo.jdisc.http.ServerConfig$DefaultFilters$Builder localPort(int)",
+ "public com.yahoo.jdisc.http.ServerConfig$DefaultFilters build()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.jdisc.http.ServerConfig$DefaultFilters": {
+ "superClass": "com.yahoo.config.InnerNode",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "final"
+ ],
+ "methods": [
+ "public void <init>(com.yahoo.jdisc.http.ServerConfig$DefaultFilters$Builder)",
+ "public java.lang.String filterId()",
+ "public int localPort()"
+ ],
+ "fields": []
+ },
"com.yahoo.jdisc.http.ServerConfig$Filter$Builder": {
"superClass": "java.lang.Object",
"interfaces": [
@@ -894,6 +928,8 @@
"public boolean removeRawPostBodyForWwwUrlEncodedPost()",
"public java.util.List filter()",
"public com.yahoo.jdisc.http.ServerConfig$Filter filter(int)",
+ "public java.util.List defaultFilters()",
+ "public com.yahoo.jdisc.http.ServerConfig$DefaultFilters defaultFilters(int)",
"public int maxWorkerThreads()",
"public int minWorkerThreads()",
"public double stopTimeout()",
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindings.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindings.java
index 301c92a4583..310f3c9a646 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindings.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindings.java
@@ -3,18 +3,15 @@ package com.yahoo.jdisc.http.server.jetty;
import com.yahoo.jdisc.application.BindingRepository;
import com.yahoo.jdisc.application.BindingSet;
-import com.yahoo.jdisc.application.UriPattern;
import com.yahoo.jdisc.http.filter.RequestFilter;
import com.yahoo.jdisc.http.filter.ResponseFilter;
import java.net.URI;
import java.util.Collection;
+import java.util.Collections;
import java.util.Map;
import java.util.Optional;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
-import static java.util.stream.Collectors.toSet;
+import java.util.TreeMap;
/**
* Resolves request/response filter (chain) from a {@link URI} instance.
@@ -24,87 +21,82 @@ import static java.util.stream.Collectors.toSet;
*/
public class FilterBindings {
- private final BindingSet<FilterHolder<RequestFilter>> requestFilters;
- private final BindingSet<FilterHolder<ResponseFilter>> responseFilters;
+ private final Map<String, RequestFilter> requestFilters;
+ private final Map<String, ResponseFilter> responseFilters;
+ private final Map<Integer, String> defaultRequestFilters;
+ private final Map<Integer, String> defaultResponseFilters;
+ private final BindingSet<String> requestFilterBindings;
+ private final BindingSet<String> responseFilterBindings;
private FilterBindings(
- BindingSet<FilterHolder<RequestFilter>> requestFilters,
- BindingSet<FilterHolder<ResponseFilter>> responseFilters) {
+ Map<String, RequestFilter> requestFilters,
+ Map<String, ResponseFilter> responseFilters,
+ Map<Integer, String> defaultRequestFilters,
+ Map<Integer, String> defaultResponseFilters,
+ BindingSet<String> requestFilterBindings,
+ BindingSet<String> responseFilterBindings) {
this.requestFilters = requestFilters;
this.responseFilters = responseFilters;
+ this.defaultRequestFilters = defaultRequestFilters;
+ this.defaultResponseFilters = defaultResponseFilters;
+ this.requestFilterBindings = requestFilterBindings;
+ this.responseFilterBindings = responseFilterBindings;
}
- public Optional<String> resolveRequestFilter(URI uri) { return resolveFilterId(requestFilters, uri); }
-
- public Optional<String> resolveResponseFilter(URI uri) { return resolveFilterId(responseFilters, uri); }
-
- public RequestFilter getRequestFilter(String filterId) { return getFilterInstance(requestFilters, filterId); }
-
- public ResponseFilter getResponseFilter(String filterId) { return getFilterInstance(responseFilters, filterId); }
-
- public Collection<String> requestFilterIds() { return filterIds(requestFilters); }
-
- public Collection<String> responseFilterIds() { return filterIds(responseFilters); }
+ public Optional<String> resolveRequestFilter(URI uri, int localPort) {
+ String filterId = requestFilterBindings.resolve(uri);
+ if (filterId != null) return Optional.of(filterId);
+ return Optional.ofNullable(defaultRequestFilters.get(localPort));
+ }
- public Collection<RequestFilter> requestFilters() { return filters(requestFilters); }
+ public Optional<String> resolveResponseFilter(URI uri, int localPort) {
+ String filterId = responseFilterBindings.resolve(uri);
+ if (filterId != null) return Optional.of(filterId);
+ return Optional.ofNullable(defaultResponseFilters.get(localPort));
+ }
- public Collection<ResponseFilter> responseFilters() { return filters(responseFilters); }
+ public RequestFilter getRequestFilter(String filterId) { return requestFilters.get(filterId); }
- private static <T> Optional<String> resolveFilterId(BindingSet<FilterHolder<T>> filters, URI uri) {
- return Optional.ofNullable(filters.resolve(uri))
- .map(holder -> holder.filterId);
- }
+ public ResponseFilter getResponseFilter(String filterId) { return responseFilters.get(filterId); }
- private static <T> T getFilterInstance(BindingSet<FilterHolder<T>> filters, String filterId) {
- return stream(filters)
- .filter(filterEntry -> filterId.equals(filterEntry.getValue().filterId))
- .map(filterEntry -> filterEntry.getValue().filterInstance)
- .findAny()
- .orElseThrow(() -> new IllegalArgumentException("No filter with id " + filterId));
- }
+ public Collection<String> requestFilterIds() { return requestFilters.keySet(); }
- private static <T> Collection<String> filterIds(BindingSet<FilterHolder<T>> filters) {
- return stream(filters)
- .map(filterEntry -> filterEntry.getValue().filterId)
- .collect(toSet());
- }
+ public Collection<String> responseFilterIds() { return responseFilters.keySet(); }
- private static <T> Collection<T> filters(BindingSet<FilterHolder<T>> filters) {
- return stream(filters)
- .map(filterEntry -> filterEntry.getValue().filterInstance)
- .collect(toSet());
- }
+ public Collection<RequestFilter> requestFilters() { return requestFilters.values(); }
- private static <T> Stream<Map.Entry<UriPattern, FilterHolder<T>>> stream(BindingSet<FilterHolder<T>> filters) {
- return StreamSupport.stream(filters.spliterator(), false);
- }
+ public Collection<ResponseFilter> responseFilters() { return responseFilters.values(); }
public static class Builder {
- private final BindingRepository<FilterHolder<RequestFilter>> requestFilters = new BindingRepository<>();
- private final BindingRepository<FilterHolder<ResponseFilter>> responseFilters = new BindingRepository<>();
+ private final Map<String, RequestFilter> requestFilters = new TreeMap<>();
+ private final Map<String, ResponseFilter> responseFilters = new TreeMap<>();
+ private final Map<Integer, String> defaultRequestFilters = new TreeMap<>();
+ private final Map<Integer, String> defaultResponseFilters = new TreeMap<>();
+ private final BindingRepository<String> requestFilterBindings = new BindingRepository<>();
+ private final BindingRepository<String> responseFilterBindings = new BindingRepository<>();
public Builder() {}
- public Builder addRequestFilter(String id, String binding, RequestFilter filter) {
- requestFilters.bind(binding, new FilterHolder<>(id, filter));
- return this;
- }
+ public Builder addRequestFilter(String id, RequestFilter filter) { requestFilters.put(id, filter); return this; }
- public Builder addResponseFilter(String id, String binding, ResponseFilter filter) {
- responseFilters.bind(binding, new FilterHolder<>(id, filter));
- return this;
- }
+ public Builder addResponseFilter(String id, ResponseFilter filter) { responseFilters.put(id, filter); return this; }
- public FilterBindings build() { return new FilterBindings(requestFilters.activate(), responseFilters.activate()); }
- }
+ public Builder addRequestFilterBinding(String id, String binding) { requestFilterBindings.bind(binding, id); return this; }
+
+ public Builder addResponseFilterBinding(String id, String binding) { responseFilterBindings.bind(binding, id); return this; }
+
+ public Builder setRequestFilterDefaultForPort(String id, int port) { defaultRequestFilters.put(port, id); return this; }
- private static class FilterHolder<T> {
- final String filterId;
- final T filterInstance;
+ public Builder setResponseFilterDefaultForPort(String id, int port) { defaultResponseFilters.put(port, id); return this; }
- FilterHolder(String filterId, T filterInstance) {
- this.filterId = filterId;
- this.filterInstance = filterInstance;
+ public FilterBindings build() {
+ return new FilterBindings(
+ Collections.unmodifiableMap(requestFilters),
+ Collections.unmodifiableMap(responseFilters),
+ Collections.unmodifiableMap(defaultRequestFilters),
+ Collections.unmodifiableMap(defaultResponseFilters),
+ requestFilterBindings.activate(),
+ responseFilterBindings.activate());
}
}
}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterResolver.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterResolver.java
new file mode 100644
index 00000000000..6590b9791d4
--- /dev/null
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterResolver.java
@@ -0,0 +1,56 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.server.jetty;
+
+import com.yahoo.jdisc.Metric;
+import com.yahoo.jdisc.http.filter.RequestFilter;
+import com.yahoo.jdisc.http.filter.ResponseFilter;
+
+import javax.servlet.http.HttpServletRequest;
+import java.net.URI;
+import java.util.Map;
+import java.util.Optional;
+
+import static com.yahoo.jdisc.http.server.jetty.JDiscHttpServlet.getConnector;
+
+/**
+ * Resolve request/response filter (chain) based on {@link FilterBindings}.
+ *
+ * @author bjorncs
+ */
+class FilterResolver {
+
+ private final FilterBindings bindings;
+ private final Metric metric;
+
+ FilterResolver(FilterBindings bindings, Metric metric) {
+ this.bindings = bindings;
+ this.metric = metric;
+ }
+
+ Optional<RequestFilter> resolveRequestFilter(HttpServletRequest servletRequest, URI jdiscUri) {
+ Optional<String> maybeFilterId = bindings.resolveRequestFilter(jdiscUri, getConnector(servletRequest).listenPort());
+ if (maybeFilterId.isPresent()) {
+ metric.add(MetricDefinitions.FILTERING_REQUEST_HANDLED, 1L, createMetricContext(servletRequest, maybeFilterId.get()));
+ } else {
+ metric.add(MetricDefinitions.FILTERING_REQUEST_UNHANDLED, 1L, createMetricContext(servletRequest, null));
+ }
+ return maybeFilterId.map(bindings::getRequestFilter);
+ }
+
+ Optional<ResponseFilter> resolveResponseFilter(HttpServletRequest servletRequest, URI jdiscUri) {
+ Optional<String> maybeFilterId = bindings.resolveResponseFilter(jdiscUri, getConnector(servletRequest).listenPort());
+ if (maybeFilterId.isPresent()) {
+ metric.add(MetricDefinitions.FILTERING_RESPONSE_HANDLED, 1L, createMetricContext(servletRequest, maybeFilterId.get()));
+ } else {
+ metric.add(MetricDefinitions.FILTERING_RESPONSE_UNHANDLED, 1L, createMetricContext(servletRequest, null));
+ }
+ return maybeFilterId.map(bindings::getResponseFilter);
+ }
+
+ private Metric.Context createMetricContext(HttpServletRequest request, String filterId) {
+ Map<String, String> extraDimensions = filterId != null
+ ? Map.of(MetricDefinitions.FILTER_CHAIN_ID_DIMENSION, filterId)
+ : Map.of();
+ return JDiscHttpServlet.getConnector(request).createRequestMetricContext(request, extraDimensions);
+ }
+}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilteringRequestHandler.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilteringRequestHandler.java
index 7f761d6ab4a..0923e6688f4 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilteringRequestHandler.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilteringRequestHandler.java
@@ -16,6 +16,7 @@ import com.yahoo.jdisc.http.core.CompletionHandlers;
import com.yahoo.jdisc.http.filter.RequestFilter;
import com.yahoo.jdisc.http.filter.ResponseFilter;
+import javax.servlet.http.HttpServletRequest;
import java.nio.ByteBuffer;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -41,10 +42,12 @@ class FilteringRequestHandler extends AbstractRequestHandler {
};
- private final FilterBindings filterBindings;
+ private final FilterResolver filterResolver;
+ private final HttpServletRequest servletRequest;
- public FilteringRequestHandler(FilterBindings filterBindings) {
- this.filterBindings = filterBindings;
+ public FilteringRequestHandler(FilterResolver filterResolver, HttpServletRequest servletRequest) {
+ this.filterResolver = filterResolver;
+ this.servletRequest = servletRequest;
}
@Override
@@ -52,12 +55,11 @@ class FilteringRequestHandler extends AbstractRequestHandler {
Preconditions.checkArgument(request instanceof HttpRequest, "Expected HttpRequest, got " + request);
Objects.requireNonNull(originalResponseHandler, "responseHandler");
- RequestFilter requestFilter = filterBindings.resolveRequestFilter(request.getUri())
- .map(filterBindings::getRequestFilter)
+ RequestFilter requestFilter = filterResolver.resolveRequestFilter(servletRequest, request.getUri())
.orElse(null);
- ResponseFilter responseFilter = filterBindings.resolveResponseFilter(request.getUri())
- .map(filterBindings::getResponseFilter)
+ ResponseFilter responseFilter = filterResolver.resolveResponseFilter(servletRequest, request.getUri())
.orElse(null);
+
// Not using request.connect() here - it adds logic for error handling that we'd rather leave to the framework.
RequestHandler resolvedRequestHandler = request.container().resolveHandler(request);
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java
index ed5095fb06f..940009e7520 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java
@@ -212,7 +212,7 @@ class HttpRequestDispatch {
AccessLogEntry accessLogEntry,
HttpServletRequest servletRequest) {
RequestHandler requestHandler = wrapHandlerIfFormPost(
- new FilteringRequestHandler(context.filterBindings),
+ new FilteringRequestHandler(context.filterResolver, servletRequest),
servletRequest, context.serverConfig.removeRawPostBodyForWwwUrlEncodedPost());
return new AccessLoggingRequestHandler(requestHandler, accessLogEntry);
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscContext.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscContext.java
index 4667ff3975b..66471587bd5 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscContext.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscContext.java
@@ -8,7 +8,7 @@ import com.yahoo.jdisc.service.CurrentContainer;
import java.util.concurrent.Executor;
public class JDiscContext {
- final FilterBindings filterBindings;
+ final FilterResolver filterResolver;
final CurrentContainer container;
final Executor janitor;
final Metric metric;
@@ -20,7 +20,7 @@ public class JDiscContext {
Metric metric,
ServerConfig serverConfig) {
- this.filterBindings = filterBindings;
+ this.filterResolver = new FilterResolver(filterBindings, metric);
this.container = container;
this.janitor = janitor;
this.metric = metric;
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscFilterInvokerFilter.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscFilterInvokerFilter.java
index 5a904299e44..e4dbccf1bcb 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscFilterInvokerFilter.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscFilterInvokerFilter.java
@@ -3,6 +3,7 @@ package com.yahoo.jdisc.http.server.jetty;
import com.yahoo.container.logging.AccessLogEntry;
import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.http.filter.RequestFilter;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncListener;
@@ -21,6 +22,7 @@ import javax.servlet.http.HttpServletResponseWrapper;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URI;
+import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
@@ -75,8 +77,7 @@ class JDiscFilterInvokerFilter implements Filter {
private void runChainAndResponseFilters(URI uri, HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
Optional<OneTimeRunnable> responseFilterInvoker =
- jDiscContext.filterBindings.resolveResponseFilter(uri)
- .map(jDiscContext.filterBindings::getResponseFilter)
+ jDiscContext.filterResolver.resolveResponseFilter(request, uri)
.map(responseFilter ->
new OneTimeRunnable(() ->
filterInvoker.invokeResponseFilterChain(responseFilter, uri, request, response)));
@@ -106,12 +107,12 @@ class JDiscFilterInvokerFilter implements Filter {
private HttpServletRequest runRequestFilterWithMatchingBinding(AtomicReference<Boolean> responseReturned, URI uri, HttpServletRequest request, HttpServletResponse response) throws IOException {
try {
- String requestFilterId = jDiscContext.filterBindings.resolveRequestFilter(uri).orElse(null);
- if (requestFilterId == null)
+ RequestFilter requestFilter = jDiscContext.filterResolver.resolveRequestFilter(request, uri).orElse(null);
+ if (requestFilter == null)
return request;
ResponseHandler responseHandler = createResponseHandler(responseReturned, request, response);
- return filterInvoker.invokeRequestFilterChain(jDiscContext.filterBindings.getRequestFilter(requestFilterId), uri, request, responseHandler);
+ return filterInvoker.invokeRequestFilterChain(requestFilter, uri, request, responseHandler);
} catch (Exception e) {
throw new RuntimeException("Failed running request filter chain for uri " + uri, e);
}
@@ -133,7 +134,7 @@ class JDiscFilterInvokerFilter implements Filter {
final AccessLogEntry accessLogEntry = null; // Not used in this context.
return new HttpRequestDispatch(jDiscContext,
accessLogEntry,
- getConnector(request).getRequestMetricContext(request),
+ getConnector(request).createRequestMetricContext(request, Map.of()),
request, response);
} catch (IOException e) {
throw throwUnchecked(e);
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java
index 8ffc6759ae7..60ecd449c15 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java
@@ -13,6 +13,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
+import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -128,7 +129,7 @@ class JDiscHttpServlet extends HttpServlet {
}
private static Metric.Context getMetricContext(HttpServletRequest request) {
- return JDiscServerConnector.fromRequest(request).getRequestMetricContext(request);
+ return JDiscServerConnector.fromRequest(request).createRequestMetricContext(request, Map.of());
}
private static String formatAttributes(final HttpServletRequest request) {
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java
index 7681bc5077d..86841458b13 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java
@@ -14,9 +14,7 @@ import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
-import java.util.Objects;
import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
/**
* @author bjorncs
@@ -25,7 +23,6 @@ class JDiscServerConnector extends ServerConnector {
public static final String REQUEST_ATTRIBUTE = JDiscServerConnector.class.getName();
private final Metric.Context metricCtx;
- private final Map<RequestDimensions, Metric.Context> requestMetricContextCache = new ConcurrentHashMap<>();
private final ConnectionStatistics statistics;
private final ConnectorConfig config;
private final boolean tcpKeepAlive;
@@ -70,20 +67,18 @@ class JDiscServerConnector extends ServerConnector {
return metricCtx;
}
- public Metric.Context getRequestMetricContext(HttpServletRequest request) {
+ public Metric.Context createRequestMetricContext(HttpServletRequest request, Map<String, String> extraDimensions) {
String method = request.getMethod();
String scheme = request.getScheme();
boolean clientAuthenticated = request.getAttribute(com.yahoo.jdisc.http.servlet.ServletRequest.SERVLET_REQUEST_X509CERT) != null;
- var requestDimensions = new RequestDimensions(method, scheme, clientAuthenticated);
- return requestMetricContextCache.computeIfAbsent(requestDimensions, ignored -> {
- Map<String, Object> dimensions = createConnectorDimensions(listenPort, connectorName);
- dimensions.put(MetricDefinitions.METHOD_DIMENSION, method);
- dimensions.put(MetricDefinitions.SCHEME_DIMENSION, scheme);
- dimensions.put(MetricDefinitions.CLIENT_AUTHENTICATED_DIMENSION, Boolean.toString(clientAuthenticated));
- String serverName = Optional.ofNullable(request.getServerName()).orElse("unknown");
- dimensions.put(MetricDefinitions.REQUEST_SERVER_NAME_DIMENSION, serverName);
- return metric.createContext(dimensions);
- });
+ Map<String, Object> dimensions = createConnectorDimensions(listenPort, connectorName);
+ dimensions.put(MetricDefinitions.METHOD_DIMENSION, method);
+ dimensions.put(MetricDefinitions.SCHEME_DIMENSION, scheme);
+ dimensions.put(MetricDefinitions.CLIENT_AUTHENTICATED_DIMENSION, Boolean.toString(clientAuthenticated));
+ String serverName = Optional.ofNullable(request.getServerName()).orElse("unknown");
+ dimensions.put(MetricDefinitions.REQUEST_SERVER_NAME_DIMENSION, serverName);
+ dimensions.putAll(extraDimensions);
+ return metric.createContext(dimensions);
}
public static JDiscServerConnector fromRequest(ServletRequest request) {
@@ -105,31 +100,4 @@ class JDiscServerConnector extends ServerConnector {
return props;
}
- private static class RequestDimensions {
- final String method;
- final String scheme;
- final boolean clientAuthenticated;
-
- RequestDimensions(String method, String scheme, boolean clientAuthenticated) {
- this.method = method;
- this.scheme = scheme;
- this.clientAuthenticated = clientAuthenticated;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- RequestDimensions that = (RequestDimensions) o;
- return clientAuthenticated == that.clientAuthenticated &&
- Objects.equals(method, that.method) &&
- Objects.equals(scheme, that.scheme);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(method, scheme, clientAuthenticated);
- }
- }
-
}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/MetricDefinitions.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/MetricDefinitions.java
index 1821f17bc49..5e953179b53 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/MetricDefinitions.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/MetricDefinitions.java
@@ -15,6 +15,7 @@ class MetricDefinitions {
static final String CLIENT_IP_DIMENSION = "clientIp";
static final String CLIENT_AUTHENTICATED_DIMENSION = "clientAuthenticated";
static final String REQUEST_SERVER_NAME_DIMENSION = "requestServerName";
+ static final String FILTER_CHAIN_ID_DIMENSION = "chainId";
static final String NUM_OPEN_CONNECTIONS = "serverNumOpenConnections";
static final String NUM_CONNECTIONS_OPEN_MAX = "serverConnectionsOpenMax";
@@ -69,5 +70,10 @@ class MetricDefinitions {
static final String JETTY_THREADPOOL_TOTAL_THREADS = "jdisc.http.jetty.threadpool.thread.total";
static final String JETTY_THREADPOOL_QUEUE_SIZE = "jdisc.http.jetty.threadpool.queue.size";
+ static final String FILTERING_REQUEST_HANDLED = "jdisc.http.filtering.request.handled";
+ static final String FILTERING_REQUEST_UNHANDLED = "jdisc.http.filtering.request.unhandled";
+ static final String FILTERING_RESPONSE_HANDLED = "jdisc.http.filtering.response.handled";
+ static final String FILTERING_RESPONSE_UNHANDLED = "jdisc.http.filtering.response.unhandled";
+
private MetricDefinitions() {}
}
diff --git a/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.jdisc.http.server.def b/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.jdisc.http.server.def
index 81f9859fabd..f33dc35ea0b 100644
--- a/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.jdisc.http.server.def
+++ b/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.jdisc.http.server.def
@@ -27,6 +27,12 @@ filter[].id string
# The binding of a filter
filter[].binding string
+# Filter id for a default filter (chain)
+defaultFilters[].filterId string
+
+# The local port which the default filter should be applied to
+defaultFilters[].localPort int
+
# Max number of threads in underlying Jetty pool
maxWorkerThreads int default = 200
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/FilterTestCase.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/FilterTestCase.java
index a978e42f7cb..fd929b3e037 100644
--- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/FilterTestCase.java
+++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/FilterTestCase.java
@@ -46,14 +46,16 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
- * @author bakksjo
+ * @author Oyvind Bakksjo
+ * @author bjorncs
*/
public class FilterTestCase {
@Test
public void requireThatRequestFilterIsNotRunOnUnboundPath() throws Exception {
RequestFilterMockBase filter = mock(RequestFilterMockBase.class);
FilterBindings filterBindings = new FilterBindings.Builder()
- .addRequestFilter("my-request-filter", "http://*/filtered/*", filter)
+ .addRequestFilter("my-request-filter", filter)
+ .addRequestFilterBinding("my-request-filter", "http://*/filtered/*")
.build();
final MyRequestHandler requestHandler = new MyRequestHandler();
final TestDriver testDriver = newDriver(requestHandler, filterBindings);
@@ -70,7 +72,8 @@ public class FilterTestCase {
public void requireThatRequestFilterIsRunOnBoundPath() throws Exception {
final RequestFilter filter = mock(RequestFilterMockBase.class);
FilterBindings filterBindings = new FilterBindings.Builder()
- .addRequestFilter("my-request-filter", "http://*/filtered/*", filter)
+ .addRequestFilter("my-request-filter", filter)
+ .addRequestFilterBinding("my-request-filter", "http://*/filtered/*")
.build();
final MyRequestHandler requestHandler = new MyRequestHandler();
final TestDriver testDriver = newDriver(requestHandler, filterBindings);
@@ -87,7 +90,8 @@ public class FilterTestCase {
public void requireThatRequestFilterChangesAreSeenByRequestHandler() throws Exception {
final RequestFilter filter = new HeaderRequestFilter("foo", "bar");
FilterBindings filterBindings = new FilterBindings.Builder()
- .addRequestFilter("my-request-filter", "http://*/*", filter)
+ .addRequestFilter("my-request-filter", filter)
+ .addRequestFilterBinding("my-request-filter", "http://*/*")
.build();
final MyRequestHandler requestHandler = new MyRequestHandler();
final TestDriver testDriver = newDriver(requestHandler, filterBindings);
@@ -103,7 +107,8 @@ public class FilterTestCase {
@Test
public void requireThatRequestFilterCanRespond() throws Exception {
FilterBindings filterBindings = new FilterBindings.Builder()
- .addRequestFilter("my-request-filter", "http://*/*", new RespondForbiddenFilter())
+ .addRequestFilter("my-request-filter", new RespondForbiddenFilter())
+ .addRequestFilterBinding("my-request-filter", "http://*/*")
.build();
final MyRequestHandler requestHandler = new MyRequestHandler();
final TestDriver testDriver = newDriver(requestHandler, filterBindings);
@@ -120,7 +125,8 @@ public class FilterTestCase {
final int responseStatus = Response.Status.OK;
final String responseMessage = "Excellent";
FilterBindings filterBindings = new FilterBindings.Builder()
- .addRequestFilter("my-request-filter", "http://*/*", new NullCompletionHandlerFilter(responseStatus, responseMessage))
+ .addRequestFilter("my-request-filter", new NullCompletionHandlerFilter(responseStatus, responseMessage))
+ .addRequestFilterBinding("my-request-filter", "http://*/*")
.build();
final MyRequestHandler requestHandler = new MyRequestHandler();
final TestDriver testDriver = newDriver(requestHandler, filterBindings);
@@ -137,7 +143,8 @@ public class FilterTestCase {
@Test
public void requireThatRequestFilterExecutionIsExceptionSafe() throws Exception {
FilterBindings filterBindings = new FilterBindings.Builder()
- .addRequestFilter("my-request-filter", "http://*/*", new ThrowingRequestFilter())
+ .addRequestFilter("my-request-filter", new ThrowingRequestFilter())
+ .addRequestFilterBinding("my-request-filter", "http://*/*")
.build();
final MyRequestHandler requestHandler = new MyRequestHandler();
final TestDriver testDriver = newDriver(requestHandler, filterBindings);
@@ -153,7 +160,8 @@ public class FilterTestCase {
public void requireThatResponseFilterIsNotRunOnUnboundPath() throws Exception {
final ResponseFilter filter = mock(ResponseFilterMockBase.class);
FilterBindings filterBindings = new FilterBindings.Builder()
- .addResponseFilter("my-response-filter", "http://*/filtered/*", filter)
+ .addResponseFilter("my-response-filter", filter)
+ .addResponseFilterBinding("my-response-filter", "http://*/filtered/*")
.build();
final MyRequestHandler requestHandler = new MyRequestHandler();
final TestDriver testDriver = newDriver(requestHandler, filterBindings);
@@ -170,7 +178,8 @@ public class FilterTestCase {
public void requireThatResponseFilterIsRunOnBoundPath() throws Exception {
final ResponseFilter filter = mock(ResponseFilterMockBase.class);
FilterBindings filterBindings = new FilterBindings.Builder()
- .addResponseFilter("my-response-filter", "http://*/filtered/*", filter)
+ .addResponseFilter("my-response-filter", filter)
+ .addResponseFilterBinding("my-response-filter", "http://*/filtered/*")
.build();
final MyRequestHandler requestHandler = new MyRequestHandler();
final TestDriver testDriver = newDriver(requestHandler, filterBindings);
@@ -186,7 +195,8 @@ public class FilterTestCase {
@Test
public void requireThatResponseFilterChangesAreWrittenToResponse() throws Exception {
FilterBindings filterBindings = new FilterBindings.Builder()
- .addResponseFilter("my-response-filter", "http://*/*", new HeaderResponseFilter("foo", "bar"))
+ .addResponseFilter("my-response-filter", new HeaderResponseFilter("foo", "bar"))
+ .addResponseFilterBinding("my-response-filter", "http://*/*")
.build();
final MyRequestHandler requestHandler = new MyRequestHandler();
final TestDriver testDriver = newDriver(requestHandler, filterBindings);
@@ -202,7 +212,8 @@ public class FilterTestCase {
@Test
public void requireThatResponseFilterExecutionIsExceptionSafe() throws Exception {
FilterBindings filterBindings = new FilterBindings.Builder()
- .addResponseFilter("my-response-filter", "http://*/*", new ThrowingResponseFilter())
+ .addResponseFilter("my-response-filter", new ThrowingResponseFilter())
+ .addResponseFilterBinding("my-response-filter", "http://*/*")
.build();
final MyRequestHandler requestHandler = new MyRequestHandler();
final TestDriver testDriver = newDriver(requestHandler, filterBindings);
@@ -220,8 +231,10 @@ public class FilterTestCase {
final ResponseFilter responseFilter = mock(ResponseFilterMockBase.class);
final String uriPattern = "http://*/*";
FilterBindings filterBindings = new FilterBindings.Builder()
- .addRequestFilter("my-request-filter", uriPattern, requestFilter)
- .addResponseFilter("my-response-filter", uriPattern, responseFilter)
+ .addRequestFilter("my-request-filter", requestFilter)
+ .addRequestFilterBinding("my-request-filter", uriPattern)
+ .addResponseFilter("my-response-filter", responseFilter)
+ .addResponseFilterBinding("my-response-filter", uriPattern)
.build();
final MyRequestHandler requestHandler = new MyRequestHandler();
final TestDriver testDriver = newDriver(requestHandler, filterBindings);
@@ -238,8 +251,10 @@ public class FilterTestCase {
@Test
public void requireThatResponseFromRequestFilterGoesThroughResponseFilter() throws Exception {
FilterBindings filterBindings = new FilterBindings.Builder()
- .addRequestFilter("my-request-filter", "http://*/*", new RespondForbiddenFilter())
- .addResponseFilter("my-response-filter", "http://*/*", new HeaderResponseFilter("foo", "bar"))
+ .addRequestFilter("my-request-filter", new RespondForbiddenFilter())
+ .addRequestFilterBinding("my-request-filter", "http://*/*")
+ .addResponseFilter("my-response-filter", new HeaderResponseFilter("foo", "bar"))
+ .addResponseFilterBinding("my-response-filter", "http://*/*")
.build();
final MyRequestHandler requestHandler = new MyRequestHandler();
final TestDriver testDriver = newDriver(requestHandler, filterBindings);
@@ -380,28 +395,146 @@ public class FilterTestCase {
assertThat(response.headers().getFirst("foo"), is("bar"));
}
- private static TestDriver newDriver(
- final MyRequestHandler requestHandler,
- FilterBindings filterBindings)
- throws IOException {
+ @Test
+ public void requireThatDefaultRequestFilterChainIsRunIfNoOtherFilterChainMatches() throws IOException, InterruptedException {
+ RequestFilter filterWithBinding = mock(RequestFilter.class);
+ RequestFilter defaultFilter = mock(RequestFilter.class);
+ String defaultFilterId = "default-request-filter";
+ FilterBindings filterBindings = new FilterBindings.Builder()
+ .addRequestFilter("my-request-filter", filterWithBinding)
+ .addRequestFilterBinding("my-request-filter", "http://*/filtered/*")
+ .addRequestFilter(defaultFilterId, defaultFilter)
+ .setRequestFilterDefaultForPort(defaultFilterId, 0)
+ .build();
+ MyRequestHandler requestHandler = new MyRequestHandler();
+ TestDriver testDriver = newDriver(requestHandler, filterBindings);
+
+ testDriver.client().get("/status.html");
+
+ assertThat(requestHandler.awaitInvocation(), is(true));
+ verify(defaultFilter, times(1)).filter(any(HttpRequest.class), any(ResponseHandler.class));
+ verify(filterWithBinding, never()).filter(any(HttpRequest.class), any(ResponseHandler.class));
+
+ assertThat(testDriver.close(), is(true));
+ }
+
+ @Test
+ public void requireThatDefaultResponseFilterChainIsRunIfNoOtherFilterChainMatches() throws IOException, InterruptedException {
+ ResponseFilter filterWithBinding = mock(ResponseFilter.class);
+ ResponseFilter defaultFilter = mock(ResponseFilter.class);
+ String defaultFilterId = "default-response-filter";
+ FilterBindings filterBindings = new FilterBindings.Builder()
+ .addResponseFilter("my-response-filter", filterWithBinding)
+ .addResponseFilterBinding("my-response-filter", "http://*/filtered/*")
+ .addResponseFilter(defaultFilterId, defaultFilter)
+ .setResponseFilterDefaultForPort(defaultFilterId, 0)
+ .build();
+ MyRequestHandler requestHandler = new MyRequestHandler();
+ TestDriver testDriver = newDriver(requestHandler, filterBindings);
+
+ testDriver.client().get("/status.html");
+
+ assertThat(requestHandler.awaitInvocation(), is(true));
+ verify(defaultFilter, times(1)).filter(any(Response.class), any(Request.class));
+ verify(filterWithBinding, never()).filter(any(Response.class), any(Request.class));
+
+ assertThat(testDriver.close(), is(true));
+ }
+
+ @Test
+ public void requireThatRequestFilterWithBindingMatchHasPrecedenceOverDefaultFilter() throws IOException, InterruptedException {
+ RequestFilterMockBase filterWithBinding = mock(RequestFilterMockBase.class);
+ RequestFilterMockBase defaultFilter = mock(RequestFilterMockBase.class);
+ String defaultFilterId = "default-request-filter";
+ FilterBindings filterBindings = new FilterBindings.Builder()
+ .addRequestFilter("my-request-filter", filterWithBinding)
+ .addRequestFilterBinding("my-request-filter", "http://*/filtered/*")
+ .addRequestFilter(defaultFilterId, defaultFilter)
+ .setRequestFilterDefaultForPort(defaultFilterId, 0)
+ .build();
+ MyRequestHandler requestHandler = new MyRequestHandler();
+ TestDriver testDriver = newDriver(requestHandler, filterBindings);
+
+ testDriver.client().get("/filtered/status.html");
+
+ assertThat(requestHandler.awaitInvocation(), is(true));
+ verify(defaultFilter, never()).filter(any(HttpRequest.class), any(ResponseHandler.class));
+ verify(filterWithBinding).filter(any(HttpRequest.class), any(ResponseHandler.class));
+
+ assertThat(testDriver.close(), is(true));
+ }
+
+ @Test
+ public void requireThatResponseFilterWithBindingMatchHasPrecedenceOverDefaultFilter() throws IOException, InterruptedException {
+ ResponseFilter filterWithBinding = mock(ResponseFilter.class);
+ ResponseFilter defaultFilter = mock(ResponseFilter.class);
+ String defaultFilterId = "default-response-filter";
+ FilterBindings filterBindings = new FilterBindings.Builder()
+ .addResponseFilter("my-response-filter", filterWithBinding)
+ .addResponseFilterBinding("my-response-filter", "http://*/filtered/*")
+ .addResponseFilter(defaultFilterId, defaultFilter)
+ .setResponseFilterDefaultForPort(defaultFilterId, 0)
+ .build();
+ MyRequestHandler requestHandler = new MyRequestHandler();
+ TestDriver testDriver = newDriver(requestHandler, filterBindings);
+
+ testDriver.client().get("/filtered/status.html");
+
+ assertThat(requestHandler.awaitInvocation(), is(true));
+ verify(defaultFilter, never()).filter(any(Response.class), any(Request.class));
+ verify(filterWithBinding, times(1)).filter(any(Response.class), any(Request.class));
+
+ assertThat(testDriver.close(), is(true));
+ }
+
+ @Test
+ public void requireThatMetricAreReported() throws IOException, InterruptedException {
+ FilterBindings filterBindings = new FilterBindings.Builder()
+ .addRequestFilter("my-request-filter", mock(RequestFilter.class))
+ .addRequestFilterBinding("my-request-filter", "http://*/*")
+ .build();
+ MetricConsumerMock metricConsumerMock = new MetricConsumerMock();
+ MyRequestHandler requestHandler = new MyRequestHandler();
+ TestDriver testDriver = newDriver(requestHandler, filterBindings, metricConsumerMock);
+
+ testDriver.client().get("/status.html");
+ assertThat(requestHandler.awaitInvocation(), is(true));
+ verify(metricConsumerMock.mockitoMock())
+ .add(MetricDefinitions.FILTERING_REQUEST_HANDLED, 1L, MetricConsumerMock.STATIC_CONTEXT);
+ verify(metricConsumerMock.mockitoMock(), never())
+ .add(MetricDefinitions.FILTERING_REQUEST_UNHANDLED, 1L, MetricConsumerMock.STATIC_CONTEXT);
+ verify(metricConsumerMock.mockitoMock(), never())
+ .add(MetricDefinitions.FILTERING_RESPONSE_HANDLED, 1L, MetricConsumerMock.STATIC_CONTEXT);
+ verify(metricConsumerMock.mockitoMock())
+ .add(MetricDefinitions.FILTERING_RESPONSE_UNHANDLED, 1L, MetricConsumerMock.STATIC_CONTEXT);
+ assertThat(testDriver.close(), is(true));
+ }
+
+ private static TestDriver newDriver(MyRequestHandler requestHandler, FilterBindings filterBindings) {
+ return newDriver(requestHandler, filterBindings, new MetricConsumerMock());
+ }
+
+ private static TestDriver newDriver(MyRequestHandler requestHandler, FilterBindings filterBindings, MetricConsumerMock metricConsumer) {
return TestDriver.newInstance(
JettyHttpServer.class,
requestHandler,
- newFilterModule(filterBindings));
+ newFilterModule(filterBindings, metricConsumer));
}
- private static com.google.inject.Module newFilterModule(FilterBindings filterBindings) {
+ private static com.google.inject.Module newFilterModule(FilterBindings filterBindings, MetricConsumerMock metricConsumer) {
return Modules.combine(
new AbstractModule() {
@Override
protected void configure() {
+
bind(FilterBindings.class).toInstance(filterBindings);
bind(ServerConfig.class).toInstance(new ServerConfig(new ServerConfig.Builder()));
bind(ConnectorConfig.class).toInstance(new ConnectorConfig(new ConnectorConfig.Builder()));
bind(ServletPathsConfig.class).toInstance(new ServletPathsConfig(new ServletPathsConfig.Builder()));
}
},
- new ConnectorFactoryRegistryModule());
+ new ConnectorFactoryRegistryModule(),
+ metricConsumer.asGuiceModule());
}
private static abstract class RequestFilterMockBase extends AbstractResource implements RequestFilter {}
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
index 01ba776fbf0..ba29bcae5cc 100644
--- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
+++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
@@ -1115,17 +1115,4 @@ public class HttpServerTest {
}
}
- private static class MetricConsumerMock {
- static final Metric.Context STATIC_CONTEXT = new Metric.Context() {};
-
- private final MetricConsumer mockitoMock = mock(MetricConsumer.class);
-
- MetricConsumerMock() {
- when(mockitoMock.createContext(anyMap())).thenReturn(STATIC_CONTEXT);
- }
-
- MetricConsumer mockitoMock() { return mockitoMock; }
- Module asGuiceModule() { return binder -> binder.bind(MetricConsumer.class).toInstance(mockitoMock); }
- }
-
}
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/MetricConsumerMock.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/MetricConsumerMock.java
new file mode 100644
index 00000000000..f839d83a800
--- /dev/null
+++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/MetricConsumerMock.java
@@ -0,0 +1,28 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.server.jetty;
+
+import com.google.inject.Module;
+import com.yahoo.jdisc.Metric;
+import com.yahoo.jdisc.application.MetricConsumer;
+
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author bjorncs
+ */
+class MetricConsumerMock {
+
+ static final Metric.Context STATIC_CONTEXT = new Metric.Context() {};
+
+ private final MetricConsumer mockitoMock = mock(MetricConsumer.class);
+
+ MetricConsumerMock() {
+ when(mockitoMock.createContext(anyMap())).thenReturn(STATIC_CONTEXT);
+ }
+
+ MetricConsumer mockitoMock() { return mockitoMock; }
+ Module asGuiceModule() { return binder -> binder.bind(MetricConsumer.class).toInstance(mockitoMock); }
+
+}
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/JDiscFilterForServletTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/JDiscFilterForServletTest.java
index 272d6fbb66c..16969a47b84 100644
--- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/JDiscFilterForServletTest.java
+++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/JDiscFilterForServletTest.java
@@ -31,6 +31,7 @@ import static org.hamcrest.CoreMatchers.is;
/**
* @author Tony Vaagenes
+ * @author bjorncs
*/
public class JDiscFilterForServletTest extends ServletTestBase {
@Test
@@ -79,14 +80,16 @@ public class JDiscFilterForServletTest extends ServletTestBase {
private TestDriver requestFilterTestDriver() throws IOException {
FilterBindings filterBindings = new FilterBindings.Builder()
- .addRequestFilter("my-request-filter", "http://*/*", new TestRequestFilter())
+ .addRequestFilter("my-request-filter", new TestRequestFilter())
+ .addRequestFilterBinding("my-request-filter", "http://*/*")
.build();
return TestDrivers.newInstance(dummyRequestHandler, bindings(filterBindings));
}
private TestDriver responseFilterTestDriver() throws IOException {
FilterBindings filterBindings = new FilterBindings.Builder()
- .addResponseFilter("my-response-filter", "http://*/*", new TestResponseFilter())
+ .addResponseFilter("my-response-filter", new TestResponseFilter())
+ .addResponseFilterBinding("my-response-filter", "http://*/*")
.build();
return TestDrivers.newInstance(dummyRequestHandler, bindings(filterBindings));
}
diff --git a/node-admin/pom.xml b/node-admin/pom.xml
index fb5f8f64acc..d636e2544c2 100644
--- a/node-admin/pom.xml
+++ b/node-admin/pom.xml
@@ -154,6 +154,11 @@
<attachBundleArtifact>true</attachBundleArtifact>
</configuration>
</plugin>
+ <plugin>
+ <!-- Explicit for IntelliJ to detect correct language level from parent -->
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ </plugin>
</plugins>
</build>
</project>
diff --git a/orchestrator-restapi/pom.xml b/orchestrator-restapi/pom.xml
index 3577580f9f6..d6911907348 100644
--- a/orchestrator-restapi/pom.xml
+++ b/orchestrator-restapi/pom.xml
@@ -38,6 +38,11 @@
<build>
<plugins>
<plugin>
+ <!-- Explicit for IntelliJ to detect correct language level from parent -->
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ </plugin>
+ <plugin>
<groupId>com.yahoo.vespa</groupId>
<artifactId>bundle-plugin</artifactId>
<extensions>true</extensions>