GH-927 Add ability to configure ignired and requestOnly http headers

Resolves #927
This commit is contained in:
Oleg Zhurakousky
2023-09-26 08:46:35 +02:00
parent dea7d95f23
commit 8f7fe8ec2b
11 changed files with 126 additions and 52 deletions

View File

@@ -16,6 +16,9 @@
package org.springframework.cloud.function.web;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.function.context.FunctionProperties;
@@ -49,6 +52,17 @@ public class FunctionHttpProperties {
*/
public String delete;
/**
* List of headers to be ignored when generating HttpHeaders (request or response).
*/
public List<String> ignoredHeaders = Collections.emptyList();
/**
* List of headers that must remain only in the request.
*/
public List<String> requestOnlyHeaders = Collections.emptyList();
public String getGet() {
return this.get;
}
@@ -80,4 +94,20 @@ public class FunctionHttpProperties {
public void setDelete(String delete) {
this.delete = delete;
}
public List<String> getIgnoredHeaders() {
return ignoredHeaders;
}
public void setIgnoredHeaders(List<String> ignoredHeaders) {
this.ignoredHeaders = ignoredHeaders;
}
public List<String> getRequestOnlyHeaders() {
return requestOnlyHeaders;
}
public void setRequestOnlyHeaders(List<String> requestOnlyHeaders) {
this.requestOnlyHeaders = requestOnlyHeaders;
}
}

View File

@@ -65,7 +65,7 @@ public class FunctionController {
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
return request.getFormData().doOnSuccess(params -> wrapper.getParams().addAll(params))
.then(Mono.defer(() -> (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper
.processRequest(wrapper, wrapper.getParams(), false)));
.processRequest(wrapper, wrapper.getParams(), false, functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders())));
}
else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition()));
@@ -82,7 +82,8 @@ public class FunctionController {
return request.getMultipartData()
.doOnSuccess(params -> wrapper.getParams().addAll(multi(params)))
.then(Mono.defer(() -> (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper
.processRequest(wrapper, wrapper.getParams(), false)));
.processRequest(wrapper, wrapper.getParams(), false,
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders())));
}
else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition()));
@@ -96,7 +97,8 @@ public class FunctionController {
@RequestBody(required = false) String body) {
FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
return (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false);
return (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false,
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
}
else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition()));
@@ -110,7 +112,8 @@ public class FunctionController {
@RequestBody(required = false) String body) {
FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("PUT", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
return (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false);
return (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false,
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
}
else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("PUT", wrapper.getFunction().getFunctionDefinition()));
@@ -124,7 +127,8 @@ public class FunctionController {
@RequestBody(required = false) String body) {
FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("DELETE", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
return (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false);
return (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false,
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
}
else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("DELETE", wrapper.getFunction().getFunctionDefinition()));
@@ -136,7 +140,8 @@ public class FunctionController {
public Publisher<?> postStream(ServerWebExchange request, @RequestBody(required = false) Flux<String> body) {
FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
return FunctionWebRequestProcessingHelper.processRequest(wrapper, body, true);
return FunctionWebRequestProcessingHelper.processRequest(wrapper, body, true,
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
}
else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition()));
@@ -149,7 +154,8 @@ public class FunctionController {
public Publisher<?> getStream(ServerWebExchange request) {
FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), true);
return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), true,
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
}
else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("GET", wrapper.getFunction().getFunctionDefinition()));
@@ -162,7 +168,8 @@ public class FunctionController {
public Mono<ResponseEntity<?>> get(ServerWebExchange request) {
FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
return (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), false);
return (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), false,
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
}
else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("GET", wrapper.getFunction().getFunctionDefinition()));

View File

@@ -42,6 +42,7 @@ import org.springframework.cloud.function.context.FunctionalSpringApplication;
import org.springframework.cloud.function.context.catalog.FunctionTypeUtils;
import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper;
import org.springframework.cloud.function.context.config.ContextFunctionCatalogInitializer;
import org.springframework.cloud.function.web.FunctionHttpProperties;
import org.springframework.cloud.function.web.constants.WebRequestConstants;
import org.springframework.cloud.function.web.util.FunctionWebRequestProcessingHelper;
import org.springframework.cloud.function.web.util.FunctionWrapper;
@@ -105,9 +106,10 @@ public class FunctionEndpointInitializer implements ApplicationContextInitialize
}
private void registerEndpoint(GenericApplicationContext context) {
context.registerBean(FunctionHttpProperties.class, () -> new FunctionHttpProperties());
context.registerBean(FunctionEndpointFactory.class,
() -> new FunctionEndpointFactory(context.getBean(FunctionProperties.class), context.getBean(FunctionCatalog.class),
context.getEnvironment()));
context.getEnvironment(), context.getBean(FunctionHttpProperties.class)));
RouterFunctionRegister.register(context);
}
@@ -208,7 +210,9 @@ class FunctionEndpointFactory {
private final FunctionProperties functionProperties;
FunctionEndpointFactory(FunctionProperties functionProperties, FunctionCatalog functionCatalog, Environment environment) {
private final FunctionHttpProperties functionHttpProperties;
FunctionEndpointFactory(FunctionProperties functionProperties, FunctionCatalog functionCatalog, Environment environment, FunctionHttpProperties functionHttpProperties) {
String handler = environment.resolvePlaceholders("${function.handler}");
if (handler.startsWith("$")) {
handler = null;
@@ -216,6 +220,7 @@ class FunctionEndpointFactory {
this.functionCatalog = functionCatalog;
this.handler = handler;
this.functionProperties = functionProperties;
this.functionHttpProperties = functionHttpProperties;
}
private FunctionInvocationWrapper extract(ServerRequest request) {
@@ -241,7 +246,8 @@ class FunctionEndpointFactory {
: FunctionTypeUtils.getRawType(FunctionTypeUtils.getGenericType(funcWrapper.getOutputType()));
FunctionWrapper wrapper = new FunctionWrapper(funcWrapper);
Mono<ResponseEntity<?>> stream = request.bodyToMono(String.class)
.flatMap(content -> (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, content, false));
.flatMap(content -> (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, content, false,
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders()));
return stream.flatMap(entity -> {
BodyBuilder builder = status(entity.getStatusCode()).headers(headers -> headers.addAll(entity.getHeaders()));

View File

@@ -95,7 +95,8 @@ public class FunctionController {
return Mono.from(result).flatMap(body -> Mono.just(builder.body(body)));
}
}
return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getParams(), false);
return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getParams(), false,
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
}
else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition()));
@@ -110,7 +111,8 @@ public class FunctionController {
String argument = StringUtils.hasText(body) ? body : "";
FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
return ((Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, argument, true)).map(response -> ResponseEntity.ok()
return ((Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, argument, true,
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders())).map(response -> ResponseEntity.ok()
.headers(response.getHeaders()).body((Publisher<?>) response.getBody()));
}
else {
@@ -123,7 +125,8 @@ public class FunctionController {
public Publisher<?> getStream(WebRequest request) {
FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), true);
return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), true,
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
}
else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("GET", wrapper.getFunction().getFunctionDefinition()));
@@ -136,7 +139,8 @@ public class FunctionController {
FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
Assert.isTrue(!wrapper.getFunction().isSupplier(), "'POST' can only be mapped to Function or Consumer");
return FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false);
return FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false,
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
}
else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition()));
@@ -148,7 +152,8 @@ public class FunctionController {
public Object put(WebRequest request, @RequestBody(required = false) String body) {
FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("PUT", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
return FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false);
return FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false,
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
}
else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("PUT", wrapper.getFunction().getFunctionDefinition()));
@@ -161,7 +166,8 @@ public class FunctionController {
FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("DELETE", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
Assert.isTrue(wrapper.getFunction().isConsumer(), "'DELETE' can only be mapped to Consumer");
FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), false);
FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), false,
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
}
else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("DELETE", wrapper.getFunction().getFunctionDefinition()));
@@ -173,7 +179,8 @@ public class FunctionController {
public Object get(WebRequest request) {
FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), false);
return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), false,
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
}
else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("GET", wrapper.getFunction().getFunctionDefinition()));

View File

@@ -21,7 +21,6 @@ import java.util.function.Supplier;
import reactor.core.publisher.Flux;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -31,11 +30,11 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.cloud.function.context.FunctionCatalog;
import org.springframework.cloud.function.context.FunctionRegistration;
import org.springframework.cloud.function.context.catalog.FunctionTypeUtils;
import org.springframework.cloud.function.web.FunctionHttpProperties;
import org.springframework.cloud.function.web.source.FunctionExporterAutoConfiguration.SourceActiveCondition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment;
import org.springframework.web.reactive.function.client.WebClient;
@@ -46,14 +45,16 @@ import org.springframework.web.reactive.function.client.WebClient;
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebClient.class)
@Conditional(SourceActiveCondition.class)
@EnableConfigurationProperties(ExporterProperties.class)
@EnableConfigurationProperties({ExporterProperties.class, FunctionHttpProperties.class})
public class FunctionExporterAutoConfiguration {
private ExporterProperties props;
private final ExporterProperties props;
@Autowired
FunctionExporterAutoConfiguration(ExporterProperties props) {
private final FunctionHttpProperties httpProps;
FunctionExporterAutoConfiguration(ExporterProperties props, FunctionHttpProperties httpProps) {
this.props = props;
this.httpProps = httpProps;
}
@Bean
@@ -66,14 +67,8 @@ public class FunctionExporterAutoConfiguration {
@Bean
@ConditionalOnProperty(prefix = "spring.cloud.function.web.export.source", name = "url")
public FunctionRegistration<Supplier<Flux<?>>> origin(WebClient.Builder builder) {
HttpSupplier supplier = new HttpSupplier(builder.build(), this.props);
HttpSupplier supplier = new HttpSupplier(builder.build(), this.props, this.httpProps);
FunctionRegistration<Supplier<Flux<?>>> registration = new FunctionRegistration<>(supplier);
Type rawType = ResolvableType.forClassWithGenerics(Supplier.class, this.props.getSource().getType()).getType();
// FunctionType functionType = FunctionType.supplier(this.props.getSource().getType()).wrap(Flux.class);
// FunctionType type = FunctionType.of(rawType);
// if (this.props.getSource().isIncludeHeaders()) {
//// type = type.message();
// }
Type type = FunctionTypeUtils.discoverFunctionTypeFromClass(HttpSupplier.class);
registration = registration.type(type);
return registration;
@@ -81,7 +76,7 @@ public class FunctionExporterAutoConfiguration {
@Bean
public RequestBuilder simpleRequestBuilder(Environment environment) {
SimpleRequestBuilder builder = new SimpleRequestBuilder(environment);
SimpleRequestBuilder builder = new SimpleRequestBuilder(environment, httpProps);
if (this.props.getSink().getUrl() != null) {
builder.setTemplateUrl(this.props.getSink().getUrl());
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import org.springframework.boot.web.reactive.context.ReactiveWebApplicationConte
import org.springframework.cloud.function.context.FunctionCatalog;
import org.springframework.cloud.function.context.FunctionRegistration;
import org.springframework.cloud.function.context.config.ContextFunctionCatalogInitializer;
import org.springframework.cloud.function.web.FunctionHttpProperties;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.util.ClassUtils;
@@ -33,6 +34,7 @@ import org.springframework.web.reactive.function.client.WebClient.Builder;
/**
* @author Dave Syer
* @author Oleg Zhurakousky
* @since 2.0
*
*/
@@ -82,7 +84,7 @@ class FunctionExporterInitializer implements ApplicationContextInitializer<Gener
private void registerExport(GenericApplicationContext context) {
context.registerBean(ExporterProperties.class, () -> new ExporterProperties());
context.registerBean(FunctionExporterAutoConfiguration.class,
() -> new FunctionExporterAutoConfiguration(context.getBean(ExporterProperties.class)));
() -> new FunctionExporterAutoConfiguration(context.getBean(ExporterProperties.class), context.getBean(FunctionHttpProperties.class)));
if (context.getBeanFactory().getBeanNamesForType(DestinationResolver.class, false, false).length == 0) {
context.registerBean(DestinationResolver.class,
() -> context.getBean(FunctionExporterAutoConfiguration.class).simpleDestinationResolver());

View File

@@ -24,6 +24,7 @@ import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.cloud.function.web.FunctionHttpProperties;
import org.springframework.cloud.function.web.util.HeaderUtils;
import org.springframework.http.HttpStatusCode;
import org.springframework.messaging.support.MessageBuilder;
@@ -44,15 +45,18 @@ public class HttpSupplier implements Supplier<Flux<?>> {
private WebClient client;
private ExporterProperties props;
private final ExporterProperties props;
private final FunctionHttpProperties httpProperties;
/**
* @param client the WebClient to use. The baseUrl should be set.
* @param props the ExporterProperties to use to parameterize the requests.
*/
public HttpSupplier(WebClient client, ExporterProperties props) {
public HttpSupplier(WebClient client, ExporterProperties props, FunctionHttpProperties httpProperties) {
this.client = client;
this.props = props;
this.httpProperties = httpProperties;
}
@Override
@@ -87,7 +91,7 @@ public class HttpSupplier implements Supplier<Flux<?>> {
}
return MessageBuilder.withPayload(payload)
.copyHeaders(HeaderUtils.fromHttp(
HeaderUtils.sanitize(response.headers().asHttpHeaders())))
HeaderUtils.sanitize(response.headers().asHttpHeaders(), this.httpProperties.getIgnoredHeaders(), this.httpProperties.getRequestOnlyHeaders())))
.setHeader("scf-sink-url", this.props.getSink().getUrl())
.setHeader("scf-func-name", this.props.getSink().getName())
.build();

View File

@@ -22,6 +22,7 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.cloud.function.web.FunctionHttpProperties;
import org.springframework.cloud.function.web.util.HeaderUtils;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpHeaders;
@@ -30,6 +31,7 @@ import org.springframework.messaging.MessageHeaders;
/**
* @author Dave Syer
* @author Oleg Zhurkousky
*
*/
class SimpleRequestBuilder implements RequestBuilder {
@@ -38,10 +40,13 @@ class SimpleRequestBuilder implements RequestBuilder {
private Map<String, String> headers = new LinkedHashMap<>();
private Environment environment;
private final Environment environment;
SimpleRequestBuilder(Environment environment) {
private final FunctionHttpProperties httpProperties;
SimpleRequestBuilder(Environment environment, FunctionHttpProperties httpProperties) {
this.environment = environment;
this.httpProperties = httpProperties;
}
@Override
@@ -51,7 +56,7 @@ class SimpleRequestBuilder implements RequestBuilder {
Message<?> message = (Message<?>) value;
incoming = message.getHeaders();
}
HttpHeaders result = HeaderUtils.fromMessage(incoming);
HttpHeaders result = HeaderUtils.fromMessage(incoming, this.httpProperties.getIgnoredHeaders());
for (String key : this.headers.keySet()) {
String header = this.headers.get(key);
header = header.replace("${destination}", destination);

View File

@@ -104,7 +104,7 @@ public final class FunctionWebRequestProcessingHelper {
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public static Publisher<?> processRequest(FunctionWrapper wrapper, Object argument, boolean eventStream) {
public static Publisher<?> processRequest(FunctionWrapper wrapper, Object argument, boolean eventStream, List<String> ignoredHeaders, List<String> requestOnlyHeaders) {
if (argument == null) {
argument = "";
}
@@ -135,10 +135,10 @@ public final class FunctionWebRequestProcessingHelper {
Mono.from((Publisher) result).subscribe();
}
return "DELETE".equals(wrapper.getMethod()) ?
Mono.empty() : Mono.just(ResponseEntity.accepted().headers(HeaderUtils.sanitize(headers)).build());
Mono.empty() : Mono.just(ResponseEntity.accepted().headers(HeaderUtils.sanitize(headers, ignoredHeaders, requestOnlyHeaders)).build());
}
BodyBuilder responseOkBuilder = ResponseEntity.ok().headers(HeaderUtils.sanitize(headers));
BodyBuilder responseOkBuilder = ResponseEntity.ok().headers(HeaderUtils.sanitize(headers, ignoredHeaders, requestOnlyHeaders));
Publisher pResult;
if (result instanceof Publisher) {
@@ -161,12 +161,12 @@ public final class FunctionWebRequestProcessingHelper {
return Mono.from(pResult).map(v -> {
if (v instanceof Iterable i) {
List aggregatedResult = (List) StreamSupport.stream(i.spliterator(), false).map(m -> {
return m instanceof Message ? processMessage(responseOkBuilder, (Message<?>) m) : m;
return m instanceof Message ? processMessage(responseOkBuilder, (Message<?>) m, ignoredHeaders) : m;
}).collect(Collectors.toList());
return responseOkBuilder.header("content-type", "application/json").body(aggregatedResult);
}
else if (v instanceof Message) {
return responseOkBuilder.body(processMessage(responseOkBuilder, (Message<?>) v));
return responseOkBuilder.body(processMessage(responseOkBuilder, (Message<?>) v, ignoredHeaders));
}
else {
return responseOkBuilder.body(v);
@@ -174,8 +174,8 @@ public final class FunctionWebRequestProcessingHelper {
});
}
private static Object processMessage(BodyBuilder responseOkBuilder, Message<?> message) {
responseOkBuilder.headers(HeaderUtils.fromMessage(message.getHeaders()));
private static Object processMessage(BodyBuilder responseOkBuilder, Message<?> message, List<String> ignoredHeaders) {
responseOkBuilder.headers(HeaderUtils.fromMessage(message.getHeaders(), ignoredHeaders));
return message.getPayload();
}

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.function.web.util;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -54,12 +55,12 @@ public final class HeaderUtils {
throw new IllegalStateException("Can't instantiate a utility class");
}
public static HttpHeaders fromMessage(MessageHeaders headers) {
public static HttpHeaders fromMessage(MessageHeaders headers, List<String> ignoredHeders) {
HttpHeaders result = new HttpHeaders();
for (String name : headers.keySet()) {
Object value = headers.get(name);
name = name.toLowerCase();
if (!IGNORED.containsKey(name)) {
if (!IGNORED.containsKey(name) && !ignoredHeders.contains(name)) {
Collection<?> values = multi(value);
for (Object object : values) {
result.set(name, object.toString());
@@ -69,18 +70,29 @@ public final class HeaderUtils {
return result;
}
public static HttpHeaders sanitize(HttpHeaders request) {
@SuppressWarnings("unchecked")
public static HttpHeaders fromMessage(MessageHeaders headers) {
return fromMessage(headers, Collections.EMPTY_LIST);
}
public static HttpHeaders sanitize(HttpHeaders request, List<String> ignoredHeders, List<String> requestOnlyHeaders) {
HttpHeaders result = new HttpHeaders();
for (String name : request.keySet()) {
List<String> value = request.get(name);
name = name.toLowerCase();
if (!IGNORED.containsKey(name) && !REQUEST_ONLY.containsKey(name)) {
if (!IGNORED.containsKey(name) && !REQUEST_ONLY.containsKey(name) && !ignoredHeders.contains(name) && !requestOnlyHeaders.contains(name)) {
result.put(name, value);
}
}
return result;
}
@SuppressWarnings("unchecked")
public static HttpHeaders sanitize(HttpHeaders request) {
return sanitize(request, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
}
public static MessageHeaders fromHttp(HttpHeaders headers) {
Map<String, Object> map = new LinkedHashMap<>();
for (String name : headers.keySet()) {

View File

@@ -54,7 +54,8 @@ import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
"spring.main.web-application-type=servlet",
"spring.cloud.function.web.path=/functions",
"spring.cloud.function.routing.enabled=true"})
"spring.cloud.function.routing.enabled=true",
"spring.cloud.function.http.ignored-headers=abc,xyz"})
@ContextConfiguration(classes = { RestApplication.class, TestConfiguration.class })
public class RoutingFunctionTests {
@@ -73,11 +74,16 @@ public class RoutingFunctionTests {
.exchange(RequestEntity.post(new URI("/functions/" + RoutingFunction.FUNCTION_NAME))
.contentType(MediaType.APPLICATION_JSON)
.header("spring.cloud.function.definition", "employee")
.header("abc", "abc")
.header("xyz", "xyz")
.body("{\"name\":\"Bob\",\"age\":25}"), String.class);
assertThat(postForEntity.getBody()).isEqualTo("{\"name\":\"Bob\",\"age\":25}");
assertThat(postForEntity.getHeaders().containsKey("x-content-type")).isTrue();
assertThat(postForEntity.getHeaders().get("x-content-type").get(0))
.isEqualTo("application/xml");
assertThat(postForEntity.getHeaders().containsKey("spring.cloud.function.definition")).isTrue();
assertThat(postForEntity.getHeaders().containsKey("abc")).isFalse();
assertThat(postForEntity.getHeaders().containsKey("xyz")).isFalse();
assertThat(postForEntity.getHeaders().get("foo").get(0)).isEqualTo("bar");
}