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; package org.springframework.cloud.function.web;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.function.context.FunctionProperties; import org.springframework.cloud.function.context.FunctionProperties;
@@ -49,6 +52,17 @@ public class FunctionHttpProperties {
*/ */
public String delete; 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() { public String getGet() {
return this.get; return this.get;
} }
@@ -80,4 +94,20 @@ public class FunctionHttpProperties {
public void setDelete(String delete) { public void setDelete(String delete) {
this.delete = 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)) { if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
return request.getFormData().doOnSuccess(params -> wrapper.getParams().addAll(params)) return request.getFormData().doOnSuccess(params -> wrapper.getParams().addAll(params))
.then(Mono.defer(() -> (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper .then(Mono.defer(() -> (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper
.processRequest(wrapper, wrapper.getParams(), false))); .processRequest(wrapper, wrapper.getParams(), false, functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders())));
} }
else { else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition())); throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition()));
@@ -82,7 +82,8 @@ public class FunctionController {
return request.getMultipartData() return request.getMultipartData()
.doOnSuccess(params -> wrapper.getParams().addAll(multi(params))) .doOnSuccess(params -> wrapper.getParams().addAll(multi(params)))
.then(Mono.defer(() -> (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper .then(Mono.defer(() -> (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper
.processRequest(wrapper, wrapper.getParams(), false))); .processRequest(wrapper, wrapper.getParams(), false,
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders())));
} }
else { else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition())); throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition()));
@@ -96,7 +97,8 @@ public class FunctionController {
@RequestBody(required = false) String body) { @RequestBody(required = false) String body) {
FunctionWrapper wrapper = wrapper(request); FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { 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 { else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition())); throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition()));
@@ -110,7 +112,8 @@ public class FunctionController {
@RequestBody(required = false) String body) { @RequestBody(required = false) String body) {
FunctionWrapper wrapper = wrapper(request); FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("PUT", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { 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 { else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("PUT", wrapper.getFunction().getFunctionDefinition())); throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("PUT", wrapper.getFunction().getFunctionDefinition()));
@@ -124,7 +127,8 @@ public class FunctionController {
@RequestBody(required = false) String body) { @RequestBody(required = false) String body) {
FunctionWrapper wrapper = wrapper(request); FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("DELETE", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { 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 { else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("DELETE", wrapper.getFunction().getFunctionDefinition())); 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) { public Publisher<?> postStream(ServerWebExchange request, @RequestBody(required = false) Flux<String> body) {
FunctionWrapper wrapper = wrapper(request); FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { 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 { else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition())); throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition()));
@@ -149,7 +154,8 @@ public class FunctionController {
public Publisher<?> getStream(ServerWebExchange request) { public Publisher<?> getStream(ServerWebExchange request) {
FunctionWrapper wrapper = wrapper(request); FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { 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 { else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("GET", wrapper.getFunction().getFunctionDefinition())); throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("GET", wrapper.getFunction().getFunctionDefinition()));
@@ -162,7 +168,8 @@ public class FunctionController {
public Mono<ResponseEntity<?>> get(ServerWebExchange request) { public Mono<ResponseEntity<?>> get(ServerWebExchange request) {
FunctionWrapper wrapper = wrapper(request); FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { 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 { else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("GET", wrapper.getFunction().getFunctionDefinition())); 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.FunctionTypeUtils;
import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper;
import org.springframework.cloud.function.context.config.ContextFunctionCatalogInitializer; 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.constants.WebRequestConstants;
import org.springframework.cloud.function.web.util.FunctionWebRequestProcessingHelper; import org.springframework.cloud.function.web.util.FunctionWebRequestProcessingHelper;
import org.springframework.cloud.function.web.util.FunctionWrapper; import org.springframework.cloud.function.web.util.FunctionWrapper;
@@ -105,9 +106,10 @@ public class FunctionEndpointInitializer implements ApplicationContextInitialize
} }
private void registerEndpoint(GenericApplicationContext context) { private void registerEndpoint(GenericApplicationContext context) {
context.registerBean(FunctionHttpProperties.class, () -> new FunctionHttpProperties());
context.registerBean(FunctionEndpointFactory.class, context.registerBean(FunctionEndpointFactory.class,
() -> new FunctionEndpointFactory(context.getBean(FunctionProperties.class), context.getBean(FunctionCatalog.class), () -> new FunctionEndpointFactory(context.getBean(FunctionProperties.class), context.getBean(FunctionCatalog.class),
context.getEnvironment())); context.getEnvironment(), context.getBean(FunctionHttpProperties.class)));
RouterFunctionRegister.register(context); RouterFunctionRegister.register(context);
} }
@@ -208,7 +210,9 @@ class FunctionEndpointFactory {
private final FunctionProperties functionProperties; 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}"); String handler = environment.resolvePlaceholders("${function.handler}");
if (handler.startsWith("$")) { if (handler.startsWith("$")) {
handler = null; handler = null;
@@ -216,6 +220,7 @@ class FunctionEndpointFactory {
this.functionCatalog = functionCatalog; this.functionCatalog = functionCatalog;
this.handler = handler; this.handler = handler;
this.functionProperties = functionProperties; this.functionProperties = functionProperties;
this.functionHttpProperties = functionHttpProperties;
} }
private FunctionInvocationWrapper extract(ServerRequest request) { private FunctionInvocationWrapper extract(ServerRequest request) {
@@ -241,7 +246,8 @@ class FunctionEndpointFactory {
: FunctionTypeUtils.getRawType(FunctionTypeUtils.getGenericType(funcWrapper.getOutputType())); : FunctionTypeUtils.getRawType(FunctionTypeUtils.getGenericType(funcWrapper.getOutputType()));
FunctionWrapper wrapper = new FunctionWrapper(funcWrapper); FunctionWrapper wrapper = new FunctionWrapper(funcWrapper);
Mono<ResponseEntity<?>> stream = request.bodyToMono(String.class) 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 -> { return stream.flatMap(entity -> {
BodyBuilder builder = status(entity.getStatusCode()).headers(headers -> headers.addAll(entity.getHeaders())); 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 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 { else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition())); throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition()));
@@ -110,7 +111,8 @@ public class FunctionController {
String argument = StringUtils.hasText(body) ? body : ""; String argument = StringUtils.hasText(body) ? body : "";
FunctionWrapper wrapper = wrapper(request); FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { 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())); .headers(response.getHeaders()).body((Publisher<?>) response.getBody()));
} }
else { else {
@@ -123,7 +125,8 @@ public class FunctionController {
public Publisher<?> getStream(WebRequest request) { public Publisher<?> getStream(WebRequest request) {
FunctionWrapper wrapper = wrapper(request); FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { 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 { else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("GET", wrapper.getFunction().getFunctionDefinition())); throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("GET", wrapper.getFunction().getFunctionDefinition()));
@@ -136,7 +139,8 @@ public class FunctionController {
FunctionWrapper wrapper = wrapper(request); FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
Assert.isTrue(!wrapper.getFunction().isSupplier(), "'POST' can only be mapped to Function or Consumer"); 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 { else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition())); 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) { public Object put(WebRequest request, @RequestBody(required = false) String body) {
FunctionWrapper wrapper = wrapper(request); FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("PUT", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { 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 { else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("PUT", wrapper.getFunction().getFunctionDefinition())); throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("PUT", wrapper.getFunction().getFunctionDefinition()));
@@ -161,7 +166,8 @@ public class FunctionController {
FunctionWrapper wrapper = wrapper(request); FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("DELETE", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("DELETE", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
Assert.isTrue(wrapper.getFunction().isConsumer(), "'DELETE' can only be mapped to Consumer"); 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 { else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("DELETE", wrapper.getFunction().getFunctionDefinition())); throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("DELETE", wrapper.getFunction().getFunctionDefinition()));
@@ -173,7 +179,8 @@ public class FunctionController {
public Object get(WebRequest request) { public Object get(WebRequest request) {
FunctionWrapper wrapper = wrapper(request); FunctionWrapper wrapper = wrapper(request);
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { 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 { else {
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("GET", wrapper.getFunction().getFunctionDefinition())); 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 reactor.core.publisher.Flux;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 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.FunctionCatalog;
import org.springframework.cloud.function.context.FunctionRegistration; import org.springframework.cloud.function.context.FunctionRegistration;
import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; 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.cloud.function.web.source.FunctionExporterAutoConfiguration.SourceActiveCondition;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
@@ -46,14 +45,16 @@ import org.springframework.web.reactive.function.client.WebClient;
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebClient.class) @ConditionalOnClass(WebClient.class)
@Conditional(SourceActiveCondition.class) @Conditional(SourceActiveCondition.class)
@EnableConfigurationProperties(ExporterProperties.class) @EnableConfigurationProperties({ExporterProperties.class, FunctionHttpProperties.class})
public class FunctionExporterAutoConfiguration { public class FunctionExporterAutoConfiguration {
private ExporterProperties props; private final ExporterProperties props;
@Autowired private final FunctionHttpProperties httpProps;
FunctionExporterAutoConfiguration(ExporterProperties props) {
FunctionExporterAutoConfiguration(ExporterProperties props, FunctionHttpProperties httpProps) {
this.props = props; this.props = props;
this.httpProps = httpProps;
} }
@Bean @Bean
@@ -66,14 +67,8 @@ public class FunctionExporterAutoConfiguration {
@Bean @Bean
@ConditionalOnProperty(prefix = "spring.cloud.function.web.export.source", name = "url") @ConditionalOnProperty(prefix = "spring.cloud.function.web.export.source", name = "url")
public FunctionRegistration<Supplier<Flux<?>>> origin(WebClient.Builder builder) { 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); 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); Type type = FunctionTypeUtils.discoverFunctionTypeFromClass(HttpSupplier.class);
registration = registration.type(type); registration = registration.type(type);
return registration; return registration;
@@ -81,7 +76,7 @@ public class FunctionExporterAutoConfiguration {
@Bean @Bean
public RequestBuilder simpleRequestBuilder(Environment environment) { public RequestBuilder simpleRequestBuilder(Environment environment) {
SimpleRequestBuilder builder = new SimpleRequestBuilder(environment); SimpleRequestBuilder builder = new SimpleRequestBuilder(environment, httpProps);
if (this.props.getSink().getUrl() != null) { if (this.props.getSink().getUrl() != null) {
builder.setTemplateUrl(this.props.getSink().getUrl()); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.FunctionCatalog;
import org.springframework.cloud.function.context.FunctionRegistration; import org.springframework.cloud.function.context.FunctionRegistration;
import org.springframework.cloud.function.context.config.ContextFunctionCatalogInitializer; import org.springframework.cloud.function.context.config.ContextFunctionCatalogInitializer;
import org.springframework.cloud.function.web.FunctionHttpProperties;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.support.GenericApplicationContext;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@@ -33,6 +34,7 @@ import org.springframework.web.reactive.function.client.WebClient.Builder;
/** /**
* @author Dave Syer * @author Dave Syer
* @author Oleg Zhurakousky
* @since 2.0 * @since 2.0
* *
*/ */
@@ -82,7 +84,7 @@ class FunctionExporterInitializer implements ApplicationContextInitializer<Gener
private void registerExport(GenericApplicationContext context) { private void registerExport(GenericApplicationContext context) {
context.registerBean(ExporterProperties.class, () -> new ExporterProperties()); context.registerBean(ExporterProperties.class, () -> new ExporterProperties());
context.registerBean(FunctionExporterAutoConfiguration.class, 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) { if (context.getBeanFactory().getBeanNamesForType(DestinationResolver.class, false, false).length == 0) {
context.registerBean(DestinationResolver.class, context.registerBean(DestinationResolver.class,
() -> context.getBean(FunctionExporterAutoConfiguration.class).simpleDestinationResolver()); () -> 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.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.cloud.function.web.FunctionHttpProperties;
import org.springframework.cloud.function.web.util.HeaderUtils; import org.springframework.cloud.function.web.util.HeaderUtils;
import org.springframework.http.HttpStatusCode; import org.springframework.http.HttpStatusCode;
import org.springframework.messaging.support.MessageBuilder; import org.springframework.messaging.support.MessageBuilder;
@@ -44,15 +45,18 @@ public class HttpSupplier implements Supplier<Flux<?>> {
private WebClient client; 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 client the WebClient to use. The baseUrl should be set.
* @param props the ExporterProperties to use to parameterize the requests. * @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.client = client;
this.props = props; this.props = props;
this.httpProperties = httpProperties;
} }
@Override @Override
@@ -87,7 +91,7 @@ public class HttpSupplier implements Supplier<Flux<?>> {
} }
return MessageBuilder.withPayload(payload) return MessageBuilder.withPayload(payload)
.copyHeaders(HeaderUtils.fromHttp( .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-sink-url", this.props.getSink().getUrl())
.setHeader("scf-func-name", this.props.getSink().getName()) .setHeader("scf-func-name", this.props.getSink().getName())
.build(); .build();

View File

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

View File

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

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.function.web.util;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -54,12 +55,12 @@ public final class HeaderUtils {
throw new IllegalStateException("Can't instantiate a utility class"); 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(); HttpHeaders result = new HttpHeaders();
for (String name : headers.keySet()) { for (String name : headers.keySet()) {
Object value = headers.get(name); Object value = headers.get(name);
name = name.toLowerCase(); name = name.toLowerCase();
if (!IGNORED.containsKey(name)) { if (!IGNORED.containsKey(name) && !ignoredHeders.contains(name)) {
Collection<?> values = multi(value); Collection<?> values = multi(value);
for (Object object : values) { for (Object object : values) {
result.set(name, object.toString()); result.set(name, object.toString());
@@ -69,18 +70,29 @@ public final class HeaderUtils {
return result; 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(); HttpHeaders result = new HttpHeaders();
for (String name : request.keySet()) { for (String name : request.keySet()) {
List<String> value = request.get(name); List<String> value = request.get(name);
name = name.toLowerCase(); 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); result.put(name, value);
} }
} }
return result; 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) { public static MessageHeaders fromHttp(HttpHeaders headers) {
Map<String, Object> map = new LinkedHashMap<>(); Map<String, Object> map = new LinkedHashMap<>();
for (String name : headers.keySet()) { 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 = { @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
"spring.main.web-application-type=servlet", "spring.main.web-application-type=servlet",
"spring.cloud.function.web.path=/functions", "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 }) @ContextConfiguration(classes = { RestApplication.class, TestConfiguration.class })
public class RoutingFunctionTests { public class RoutingFunctionTests {
@@ -73,11 +74,16 @@ public class RoutingFunctionTests {
.exchange(RequestEntity.post(new URI("/functions/" + RoutingFunction.FUNCTION_NAME)) .exchange(RequestEntity.post(new URI("/functions/" + RoutingFunction.FUNCTION_NAME))
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.header("spring.cloud.function.definition", "employee") .header("spring.cloud.function.definition", "employee")
.header("abc", "abc")
.header("xyz", "xyz")
.body("{\"name\":\"Bob\",\"age\":25}"), String.class); .body("{\"name\":\"Bob\",\"age\":25}"), String.class);
assertThat(postForEntity.getBody()).isEqualTo("{\"name\":\"Bob\",\"age\":25}"); assertThat(postForEntity.getBody()).isEqualTo("{\"name\":\"Bob\",\"age\":25}");
assertThat(postForEntity.getHeaders().containsKey("x-content-type")).isTrue(); assertThat(postForEntity.getHeaders().containsKey("x-content-type")).isTrue();
assertThat(postForEntity.getHeaders().get("x-content-type").get(0)) assertThat(postForEntity.getHeaders().get("x-content-type").get(0))
.isEqualTo("application/xml"); .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"); assertThat(postForEntity.getHeaders().get("foo").get(0)).isEqualTo("bar");
} }