diff options
author | Bjørn Christian Seime <bjorncs@verizonmedia.com> | 2020-11-11 15:53:51 +0100 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@verizonmedia.com> | 2020-11-11 15:55:32 +0100 |
commit | 5c3a55faac3ae620a51f6b0388f8e1d091e725f0 (patch) | |
tree | 8fec1bb46dafc7f218e8ffd314a0b9f46f733198 /jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty | |
parent | 1b7e6a0dfb0b8686aeddce7418d28bbdfd4909c1 (diff) |
Remove implicit and potensially false dependency on jdisc_http_service
Required for later changes to jdisc_http_service. Removes jdisc_http_service from provided-dependencies.
Diffstat (limited to 'jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty')
4 files changed, 398 insertions, 0 deletions
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 new file mode 100644 index 00000000000..4dabed41bc6 --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindings.java @@ -0,0 +1,30 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.server; + +import com.yahoo.jdisc.application.BindingRepository; +import com.yahoo.jdisc.http.filter.RequestFilter; +import com.yahoo.jdisc.http.filter.ResponseFilter; + +/** + * @author bakksjo + */ +public class FilterBindings { + + private final BindingRepository<RequestFilter> requestFilters; + private final BindingRepository<ResponseFilter> responseFilters; + + public FilterBindings(BindingRepository<RequestFilter> requestFilters, + BindingRepository<ResponseFilter> responseFilters) { + this.requestFilters = requestFilters; + this.responseFilters = responseFilters; + } + + public BindingRepository<RequestFilter> getRequestFilters() { + return requestFilters; + } + + public BindingRepository<ResponseFilter> getResponseFilters() { + return responseFilters; + } + +} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindingsProvider.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindingsProvider.java new file mode 100644 index 00000000000..4f94b18f5bc --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindingsProvider.java @@ -0,0 +1,62 @@ +// 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.provider.ComponentRegistry; +import com.yahoo.container.di.componentgraph.Provider; +import com.yahoo.jdisc.http.server.jetty.FilterChainRepository; +import com.yahoo.jdisc.application.BindingRepository; +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.server.jetty.FilterBindings; + +import java.util.ArrayList; +import java.util.List; + +/** + * Provides filter bindings based on vespa config. + * + * @author bakksjo + */ +public class FilterBindingsProvider implements Provider<FilterBindings> { + + final BindingRepository<RequestFilter> requestFilters = new BindingRepository<>(); + final BindingRepository<ResponseFilter> responseFilters = new BindingRepository<>(); + + public FilterBindingsProvider(ComponentId componentId, + ServerConfig config, + FilterChainRepository filterChainRepository, + ComponentRegistry<SecurityRequestFilter> legacyRequestFilters) { + ComponentId serverId = componentId.getNamespace(); + try { + FilterUtil.setupFilters( + componentId, + legacyRequestFilters, + toFilterSpecs(config.filter()), + filterChainRepository, + requestFilters, + responseFilters); + } catch (Exception e) { + throw new RuntimeException("Invalid config for http server " + serverId, 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())); + } + return outFilters; + } + + @Override + public FilterBindings get() { + return new FilterBindings(requestFilters, responseFilters); + } + + @Override + public void deconstruct() {} + +} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterChainRepository.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterChainRepository.java new file mode 100644 index 00000000000..31bceca9337 --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterChainRepository.java @@ -0,0 +1,201 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.http.filter; + +import com.yahoo.component.AbstractComponent; +import com.yahoo.component.ComponentId; +import com.yahoo.component.ComponentSpecification; +import com.yahoo.component.chain.Chain; +import com.yahoo.component.chain.ChainedComponent; +import com.yahoo.component.chain.ChainsConfigurer; +import com.yahoo.component.chain.model.ChainsModel; +import com.yahoo.component.chain.model.ChainsModelBuilder; +import com.yahoo.component.provider.ComponentRegistry; +import com.yahoo.container.core.ChainsConfig; +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.filter.SecurityResponseFilter; +import com.yahoo.jdisc.http.filter.SecurityResponseFilterChain; +import com.yahoo.jdisc.http.filter.chain.RequestFilterChain; +import com.yahoo.jdisc.http.filter.chain.ResponseFilterChain; +import com.yahoo.processing.execution.chain.ChainRegistry; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.logging.Logger; + +import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; + +/** + * Creates JDisc request/response filter chains. + * + * @author Tony Vaagenes + * @author bjorncs + */ +public class FilterChainRepository extends AbstractComponent { + private static final Logger log = Logger.getLogger(FilterChainRepository.class.getName()); + + private final ComponentRegistry<Object> filterAndChains; + + public FilterChainRepository(ChainsConfig chainsConfig, + ComponentRegistry<RequestFilter> requestFilters, + ComponentRegistry<ResponseFilter> responseFilters, + ComponentRegistry<SecurityRequestFilter> securityRequestFilters, + ComponentRegistry<SecurityResponseFilter> securityResponseFilters) { + ComponentRegistry<Object> filterAndChains = new ComponentRegistry<>(); + addAllFilters(filterAndChains, requestFilters, responseFilters, securityRequestFilters, securityResponseFilters); + addAllChains(filterAndChains, chainsConfig, requestFilters, responseFilters, securityRequestFilters, securityResponseFilters); + filterAndChains.freeze(); + this.filterAndChains = filterAndChains; + } + + public Object getFilter(ComponentSpecification componentSpecification) { + return filterAndChains.getComponent(componentSpecification); + } + + private static void addAllFilters(ComponentRegistry<Object> destination, + ComponentRegistry<?>... registries) { + for (ComponentRegistry<?> registry : registries) { + registry.allComponentsById() + .forEach((id, filter) -> destination.register(id, wrapIfSecurityFilter(filter))); + } + } + + private static void addAllChains(ComponentRegistry<Object> destination, + ChainsConfig chainsConfig, + ComponentRegistry<?>... filters) { + ChainRegistry<FilterWrapper> chainRegistry = buildChainRegistry(chainsConfig, filters); + chainRegistry.allComponents() + .forEach(chain -> destination.register(chain.getId(), toJDiscChain(chain))); + } + + private static ChainRegistry<FilterWrapper> buildChainRegistry(ChainsConfig chainsConfig, + ComponentRegistry<?>... filters) { + ChainRegistry<FilterWrapper> chainRegistry = new ChainRegistry<>(); + ChainsModel chainsModel = ChainsModelBuilder.buildFromConfig(chainsConfig); + ChainsConfigurer.prepareChainRegistry(chainRegistry, chainsModel, allFiltersWrapped(filters)); + removeEmptyChains(chainRegistry); + chainRegistry.freeze(); + return chainRegistry; + } + + private static void removeEmptyChains(ChainRegistry<FilterWrapper> chainRegistry) { + chainRegistry.allComponents().stream() + .filter(chain -> chain.components().isEmpty()) + .map(Chain::getId) + .peek(id -> log.warning("Removing empty filter chain: " + id)) + .forEach(chainRegistry::unregister); + } + + @SuppressWarnings("unchecked") + private static Object toJDiscChain(Chain<FilterWrapper> chain) { + if (chain.components().isEmpty()) + throw new IllegalArgumentException("Empty filter chain: " + chain.getId()); + checkFilterTypesCompatible(chain); + List<?> jdiscFilters = chain.components().stream() + .map(filterWrapper -> filterWrapper.filter) + .collect(toList()); + List<?> wrappedFilters = wrapSecurityFilters(jdiscFilters); + Object head = wrappedFilters.get(0); + if (wrappedFilters.size() == 1) return head; + else if (head instanceof RequestFilter) + return RequestFilterChain.newInstance((List<RequestFilter>) wrappedFilters); + else if (head instanceof ResponseFilter) + return ResponseFilterChain.newInstance((List<ResponseFilter>) wrappedFilters); + throw new IllegalStateException(); + } + + private static List<?> wrapSecurityFilters(List<?> filters) { + List<Object> aggregatedSecurityFilters = new ArrayList<>(); + List<Object> wrappedFilters = new ArrayList<>(); + for (Object filter : filters) { + if (isSecurityFilter(filter)) { + aggregatedSecurityFilters.add(filter); + } else { + if (!aggregatedSecurityFilters.isEmpty()) { + wrappedFilters.add(createSecurityChain(aggregatedSecurityFilters)); + aggregatedSecurityFilters.clear(); + } + wrappedFilters.add(filter); + } + } + if (!aggregatedSecurityFilters.isEmpty()) { + wrappedFilters.add(createSecurityChain(aggregatedSecurityFilters)); + } + return wrappedFilters; + } + + private static void checkFilterTypesCompatible(Chain<FilterWrapper> chain) { + Set<ComponentId> requestFilters = chain.components().stream() + .filter(filter -> filter instanceof RequestFilter || filter instanceof SecurityRequestFilter) + .map(FilterWrapper::getId) + .collect(toSet()); + Set<ComponentId> responseFilters = chain.components().stream() + .filter(filter -> filter instanceof ResponseFilter || filter instanceof SecurityResponseFilter) + .map(FilterWrapper::getId) + .collect(toSet()); + if (!requestFilters.isEmpty() && !responseFilters.isEmpty()) { + throw new IllegalArgumentException( + String.format( + "Can't mix request and response filters in chain %s: request filters: %s, response filters: %s.", + chain.getId(), requestFilters, responseFilters)); + } + } + + private static ComponentRegistry<FilterWrapper> allFiltersWrapped(ComponentRegistry<?>... registries) { + ComponentRegistry<FilterWrapper> wrappedFilters = new ComponentRegistry<>(); + for (ComponentRegistry<?> registry : registries) { + registry.allComponentsById() + .forEach((id, filter) -> wrappedFilters.register(id, new FilterWrapper(id, filter))); + } + wrappedFilters.freeze(); + return wrappedFilters; + } + + private static Object wrapIfSecurityFilter(Object filter) { + if (isSecurityFilter(filter)) return createSecurityChain(Collections.singletonList(filter)); + return filter; + } + + @SuppressWarnings("unchecked") + private static Object createSecurityChain(List<?> filters) { + Object head = filters.get(0); + if (head instanceof SecurityRequestFilter) + return SecurityRequestFilterChain.newInstance((List<SecurityRequestFilter>) filters); + else if (head instanceof SecurityResponseFilter) + return SecurityResponseFilterChain.newInstance((List<SecurityResponseFilter>) filters); + throw new IllegalArgumentException("Unexpected class " + head.getClass()); + } + + private static boolean isSecurityFilter(Object filter) { + return filter instanceof SecurityRequestFilter || filter instanceof SecurityResponseFilter; + } + + private static class FilterWrapper extends ChainedComponent { + public final Object filter; + public final Class<?> filterType; + + public FilterWrapper(ComponentId id, Object filter) { + super(id); + this.filter = filter; + this.filterType = getFilterType(filter); + } + + private static Class<?> getFilterType(Object filter) { + if (filter instanceof RequestFilter) + return RequestFilter.class; + else if (filter instanceof ResponseFilter) + return ResponseFilter.class; + else if (filter instanceof SecurityRequestFilter) + return SecurityRequestFilter.class; + else if (filter instanceof SecurityResponseFilter) + return SecurityResponseFilter.class; + throw new IllegalArgumentException("Unsupported filter type: " + filter.getClass().getName()); + } + } +} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterUtil.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterUtil.java new file mode 100644 index 00000000000..39139ed5927 --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterUtil.java @@ -0,0 +1,105 @@ +// 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.jdisc.http.server.jetty.FilterChainRepository; +import com.yahoo.jdisc.application.BindingRepository; +import com.yahoo.jdisc.application.UriPattern; +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 java.util.List; + +/** + * Helper class to set up filter binding repositories based on config. + * + * @author Øyvind Bakksjø + */ +class FilterUtil { + + private static final ComponentId SEARCH_SERVER_COMPONENT_ID = ComponentId.fromString("SearchServer"); + + private final BindingRepository<RequestFilter> requestFilters; + private final BindingRepository<ResponseFilter> responseFilters; + + private FilterUtil(BindingRepository<RequestFilter> requestFilters, BindingRepository<ResponseFilter> responseFilters) { + this.requestFilters = requestFilters; + this.responseFilters = responseFilters; + } + + 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()); + } + } + + private void addFilter(Object filter, String binding) { + 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) { + requestFilters.put(new UriPattern(binding), (RequestFilter) filter); + } else if (filter instanceof ResponseFilter) { + responseFilters.put(new UriPattern(binding), (ResponseFilter) filter); + } else { + throw new RuntimeException("Unknown filter type " + filter.getClass().getName()); + } + } + + //TVT: remove + private void configureLegacyFilters(ComponentId id, ComponentRegistry<SecurityRequestFilter> legacyRequestFilters) { + ComponentId serverName = id.getNamespace(); + if (SEARCH_SERVER_COMPONENT_ID.equals(serverName) && !legacyRequestFilters.allComponents().isEmpty()) { + requestFilters.bind("http://*/*", + SecurityRequestFilterChain.newInstance(legacyRequestFilters.allComponents())); + } + } + + /** + * Populates binding repositories with filters based on config. + * + * @param requestFilters output argument that will be mutated + * @param responseFilters output argument that will be mutated + */ + public static void setupFilters(ComponentId componentId, + ComponentRegistry<SecurityRequestFilter> legacyRequestFilters, + List<FilterSpec> filtersConfig, + FilterChainRepository filterChainRepository, + BindingRepository<RequestFilter> requestFilters, + BindingRepository<ResponseFilter> responseFilters) { + FilterUtil filterUtil = new FilterUtil(requestFilters, responseFilters); + + // TODO: remove + filterUtil.configureLegacyFilters(componentId, legacyRequestFilters); + + filterUtil.configureFilters(filtersConfig, filterChainRepository); + } + + 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; + } + } + +} |