diff --git a/docs/modules/ROOT/pages/spring-cloud-function/standalone-web-applications.adoc b/docs/modules/ROOT/pages/spring-cloud-function/standalone-web-applications.adoc index 583877d19..d80f6bcb4 100644 --- a/docs/modules/ROOT/pages/spring-cloud-function/standalone-web-applications.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-function/standalone-web-applications.adoc @@ -98,6 +98,11 @@ This will only export function `foo` and function `bar` regardless how many func This will only export function composition `foo|bar` and function `baz` regardless how many functions are available in catalog (e.g., `localhost:8080/foo,bar`). +== Http Headers propagation + +By default most request `HttpHeaders` are copied into the response `HttpHeaders`. If you require to filter out certain headers you can provide the names of those headers using +`spring.cloud.function.http.ignored-headers` delimited by comas. For example, `spring.cloud.function.http.ignored-headers=foo,bar` + [[crud-rest-with-spring-cloud-function]] == CRUD REST with Spring Cloud Function diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/FunctionHttpProperties.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/FunctionHttpProperties.java index c6b9bd87a..87809b5b2 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/FunctionHttpProperties.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/FunctionHttpProperties.java @@ -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 ignoredHeaders = Collections.emptyList(); + + /** + * List of headers that must remain only in the request. + */ + public List requestOnlyHeaders = Collections.emptyList(); + public String getGet() { return this.get; } @@ -80,4 +94,20 @@ public String getDelete() { public void setDelete(String delete) { this.delete = delete; } + + public List getIgnoredHeaders() { + return ignoredHeaders; + } + + public void setIgnoredHeaders(List ignoredHeaders) { + this.ignoredHeaders = ignoredHeaders; + } + + public List getRequestOnlyHeaders() { + return requestOnlyHeaders; + } + + public void setRequestOnlyHeaders(List requestOnlyHeaders) { + this.requestOnlyHeaders = requestOnlyHeaders; + } } diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionController.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionController.java index 239ac546e..98666d756 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionController.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionController.java @@ -65,7 +65,7 @@ public Mono> form(ServerWebExchange request) { if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { return request.getFormData().doOnSuccess(params -> wrapper.getParams().addAll(params)) .then(Mono.defer(() -> (Mono>) 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 Mono> multipart(ServerWebExchange request) { return request.getMultipartData() .doOnSuccess(params -> wrapper.getParams().addAll(multi(params))) .then(Mono.defer(() -> (Mono>) 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 Mono> post(ServerWebExchange request, @RequestBody(required = false) String body) { FunctionWrapper wrapper = wrapper(request); if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { - return (Mono>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false); + return (Mono>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false, + functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders()); } else { throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition())); @@ -110,7 +112,8 @@ public Mono> put(ServerWebExchange request, @RequestBody(required = false) String body) { FunctionWrapper wrapper = wrapper(request); if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("PUT", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { - return (Mono>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false); + return (Mono>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false, + functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders()); } else { throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("PUT", wrapper.getFunction().getFunctionDefinition())); @@ -124,7 +127,8 @@ public Mono> delete(ServerWebExchange request, @RequestBody(required = false) String body) { FunctionWrapper wrapper = wrapper(request); if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("DELETE", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { - return (Mono>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false); + return (Mono>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false, + functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders()); } else { throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("DELETE", wrapper.getFunction().getFunctionDefinition())); @@ -136,7 +140,8 @@ public Mono> delete(ServerWebExchange request, public Publisher postStream(ServerWebExchange request, @RequestBody(required = false) Flux 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 Publisher postStream(ServerWebExchange request, @RequestBody(required 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 Publisher getStream(ServerWebExchange request) { public Mono> get(ServerWebExchange request) { FunctionWrapper wrapper = wrapper(request); if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { - return (Mono>) FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), false); + return (Mono>) FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), false, + functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders()); } else { throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("GET", wrapper.getFunction().getFunctionDefinition())); diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializer.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializer.java index 969ad6ca5..4696c1d52 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializer.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializer.java @@ -42,6 +42,7 @@ 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 @@ private void registerWebFluxAutoConfiguration(GenericApplicationContext context) } 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 @@ public RouterFunction functionEndpoints() { : FunctionTypeUtils.getRawType(FunctionTypeUtils.getGenericType(funcWrapper.getOutputType())); FunctionWrapper wrapper = new FunctionWrapper(funcWrapper); Mono> stream = request.bodyToMono(String.class) - .flatMap(content -> (Mono>) FunctionWebRequestProcessingHelper.processRequest(wrapper, content, false)); + .flatMap(content -> (Mono>) FunctionWebRequestProcessingHelper.processRequest(wrapper, content, false, + functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders())); return stream.flatMap(entity -> { BodyBuilder builder = status(entity.getStatusCode()).headers(headers -> headers.addAll(entity.getHeaders())); diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/mvc/FunctionController.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/mvc/FunctionController.java index 0e5977e7b..f0e5ee673 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/mvc/FunctionController.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/mvc/FunctionController.java @@ -95,7 +95,8 @@ public Object form(WebRequest request) { 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 Mono>> postStream(WebRequest request, String argument = StringUtils.hasText(body) ? body : ""; FunctionWrapper wrapper = wrapper(request); if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { - return ((Mono>) FunctionWebRequestProcessingHelper.processRequest(wrapper, argument, true)).map(response -> ResponseEntity.ok() + return ((Mono>) 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 Mono>> postStream(WebRequest request, 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 Object post(WebRequest request, @RequestBody(required = false) String bod 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 Object post(WebRequest request, @RequestBody(required = false) String bod 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 void delete(WebRequest request, @RequestBody(required = false) String bod 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 void delete(WebRequest request, @RequestBody(required = false) String bod 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())); diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/FunctionExporterAutoConfiguration.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/FunctionExporterAutoConfiguration.java index 9d65de859..c1123aa78 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/FunctionExporterAutoConfiguration.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/FunctionExporterAutoConfiguration.java @@ -21,7 +21,6 @@ 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.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 @@ @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 SupplierExporter sourceForwarder(RequestBuilder requestBuilder, Destinati @Bean @ConditionalOnProperty(prefix = "spring.cloud.function.web.export.source", name = "url") public FunctionRegistration>> origin(WebClient.Builder builder) { - HttpSupplier supplier = new HttpSupplier(builder.build(), this.props); + HttpSupplier supplier = new HttpSupplier(builder.build(), this.props, this.httpProps); FunctionRegistration>> 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 FunctionRegistration>> origin(WebClient.Builder builder) @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()); } diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/FunctionExporterInitializer.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/FunctionExporterInitializer.java index 41e906d1d..640cf930f 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/FunctionExporterInitializer.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/FunctionExporterInitializer.java @@ -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.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 @@ /** * @author Dave Syer + * @author Oleg Zhurakousky * @since 2.0 * */ @@ -82,7 +84,7 @@ private boolean isExporting(GenericApplicationContext context) { 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()); diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/HttpSupplier.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/HttpSupplier.java index e22ad1929..84574187e 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/HttpSupplier.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/HttpSupplier.java @@ -24,6 +24,7 @@ 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> { 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 @@ private Object message(ClientResponse response, Object payload) { } 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(); diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/SimpleRequestBuilder.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/SimpleRequestBuilder.java index abc3f600f..3657f057a 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/SimpleRequestBuilder.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/SimpleRequestBuilder.java @@ -22,6 +22,7 @@ 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 @@ /** * @author Dave Syer + * @author Oleg Zhurkousky * */ class SimpleRequestBuilder implements RequestBuilder { @@ -38,10 +40,13 @@ class SimpleRequestBuilder implements RequestBuilder { private Map 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 @@ public HttpHeaders headers(String destination, Object value) { 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); diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/util/FunctionWebRequestProcessingHelper.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/util/FunctionWebRequestProcessingHelper.java index 2f92cb1b8..6af5bfb7e 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/util/FunctionWebRequestProcessingHelper.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/util/FunctionWebRequestProcessingHelper.java @@ -104,7 +104,7 @@ public static String buildBadMappingErrorMessage(String httpMethod, String funct } @SuppressWarnings({ "rawtypes", "unchecked" }) - public static Publisher processRequest(FunctionWrapper wrapper, Object argument, boolean eventStream) { + public static Publisher processRequest(FunctionWrapper wrapper, Object argument, boolean eventStream, List ignoredHeaders, List requestOnlyHeaders) { if (argument == null) { argument = ""; } @@ -135,10 +135,10 @@ public static Publisher processRequest(FunctionWrapper wrapper, Object argume 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 static Publisher processRequest(FunctionWrapper wrapper, Object argume 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 @@ else if (v instanceof Message) { }); } - private static Object processMessage(BodyBuilder responseOkBuilder, Message message) { - responseOkBuilder.headers(HeaderUtils.fromMessage(message.getHeaders())); + private static Object processMessage(BodyBuilder responseOkBuilder, Message message, List ignoredHeaders) { + responseOkBuilder.headers(HeaderUtils.fromMessage(message.getHeaders(), ignoredHeaders)); return message.getPayload(); } diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/util/HeaderUtils.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/util/HeaderUtils.java index f16c1afb2..3c8fbb56f 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/util/HeaderUtils.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/util/HeaderUtils.java @@ -18,6 +18,7 @@ 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 @@ private HeaderUtils() { throw new IllegalStateException("Can't instantiate a utility class"); } - public static HttpHeaders fromMessage(MessageHeaders headers) { + public static HttpHeaders fromMessage(MessageHeaders headers, List 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 static HttpHeaders fromMessage(MessageHeaders headers) { 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 ignoredHeders, List requestOnlyHeaders) { HttpHeaders result = new HttpHeaders(); for (String name : request.keySet()) { List 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 map = new LinkedHashMap<>(); for (String name : headers.keySet()) { diff --git a/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/mvc/RoutingFunctionTests.java b/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/mvc/RoutingFunctionTests.java index 53c90d0b2..e032af809 100644 --- a/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/mvc/RoutingFunctionTests.java +++ b/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/mvc/RoutingFunctionTests.java @@ -54,7 +54,8 @@ @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 void testFunctionMessage() throws Exception { .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"); }