Skip to content

Commit

Permalink
GH-927 Add ability to configure ignored and requestOnly http headers
Browse files Browse the repository at this point in the history
Resolves #927

add docs
  • Loading branch information
olegz committed Sep 26, 2023
1 parent 479c387 commit 89da912
Show file tree
Hide file tree
Showing 12 changed files with 131 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -80,4 +94,20 @@ public String getDelete() {
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public Mono<ResponseEntity<?>> 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<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()));
Expand All @@ -82,7 +82,8 @@ public Mono<ResponseEntity<?>> multipart(ServerWebExchange request) {
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()));
Expand All @@ -96,7 +97,8 @@ public Mono<ResponseEntity<?>> post(ServerWebExchange request,
@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()));
Expand All @@ -110,7 +112,8 @@ public Mono<ResponseEntity<?>> put(ServerWebExchange request,
@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()));
Expand All @@ -124,7 +127,8 @@ public Mono<ResponseEntity<?>> delete(ServerWebExchange request,
@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()));
Expand All @@ -136,7 +140,8 @@ public Mono<ResponseEntity<?>> delete(ServerWebExchange request,
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()));
Expand All @@ -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()));
Expand All @@ -162,7 +168,8 @@ public Publisher<?> getStream(ServerWebExchange request) {
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()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -208,14 +210,17 @@ 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;
}
this.functionCatalog = functionCatalog;
this.handler = handler;
this.functionProperties = functionProperties;
this.functionHttpProperties = functionHttpProperties;
}

private FunctionInvocationWrapper extract(ServerRequest request) {
Expand All @@ -241,7 +246,8 @@ public <T> RouterFunction<?> functionEndpoints() {
: 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()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand All @@ -110,7 +111,8 @@ public Mono<ResponseEntity<Publisher<?>>> postStream(WebRequest request,
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 {
Expand All @@ -123,7 +125,8 @@ public Mono<ResponseEntity<Publisher<?>>> 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()));
Expand All @@ -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()));
Expand All @@ -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()));
Expand All @@ -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()));
Expand All @@ -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()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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
Expand All @@ -66,22 +67,16 @@ public SupplierExporter sourceForwarder(RequestBuilder requestBuilder, Destinati
@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;
}

@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());
}
Expand Down
Loading

0 comments on commit 89da912

Please sign in to comment.