Skip to content

Commit 89da912

Browse files
committed
GH-927 Add ability to configure ignored and requestOnly http headers
Resolves #927 add docs
1 parent 479c387 commit 89da912

File tree

12 files changed

+131
-52
lines changed

12 files changed

+131
-52
lines changed

docs/modules/ROOT/pages/spring-cloud-function/standalone-web-applications.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ This will only export function `foo` and function `bar` regardless how many func
9898

9999
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`).
100100

101+
== Http Headers propagation
102+
103+
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
104+
`spring.cloud.function.http.ignored-headers` delimited by comas. For example, `spring.cloud.function.http.ignored-headers=foo,bar`
105+
101106
[[crud-rest-with-spring-cloud-function]]
102107
== CRUD REST with Spring Cloud Function
103108

spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/FunctionHttpProperties.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.cloud.function.web;
1818

19+
import java.util.Collections;
20+
import java.util.List;
21+
1922
import org.springframework.boot.context.properties.ConfigurationProperties;
2023
import org.springframework.cloud.function.context.FunctionProperties;
2124

@@ -49,6 +52,17 @@ public class FunctionHttpProperties {
4952
*/
5053
public String delete;
5154

55+
56+
/**
57+
* List of headers to be ignored when generating HttpHeaders (request or response).
58+
*/
59+
public List<String> ignoredHeaders = Collections.emptyList();
60+
61+
/**
62+
* List of headers that must remain only in the request.
63+
*/
64+
public List<String> requestOnlyHeaders = Collections.emptyList();
65+
5266
public String getGet() {
5367
return this.get;
5468
}
@@ -80,4 +94,20 @@ public String getDelete() {
8094
public void setDelete(String delete) {
8195
this.delete = delete;
8296
}
97+
98+
public List<String> getIgnoredHeaders() {
99+
return ignoredHeaders;
100+
}
101+
102+
public void setIgnoredHeaders(List<String> ignoredHeaders) {
103+
this.ignoredHeaders = ignoredHeaders;
104+
}
105+
106+
public List<String> getRequestOnlyHeaders() {
107+
return requestOnlyHeaders;
108+
}
109+
110+
public void setRequestOnlyHeaders(List<String> requestOnlyHeaders) {
111+
this.requestOnlyHeaders = requestOnlyHeaders;
112+
}
83113
}

spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionController.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public Mono<ResponseEntity<?>> form(ServerWebExchange request) {
6565
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
6666
return request.getFormData().doOnSuccess(params -> wrapper.getParams().addAll(params))
6767
.then(Mono.defer(() -> (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper
68-
.processRequest(wrapper, wrapper.getParams(), false)));
68+
.processRequest(wrapper, wrapper.getParams(), false, functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders())));
6969
}
7070
else {
7171
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition()));
@@ -82,7 +82,8 @@ public Mono<ResponseEntity<?>> multipart(ServerWebExchange request) {
8282
return request.getMultipartData()
8383
.doOnSuccess(params -> wrapper.getParams().addAll(multi(params)))
8484
.then(Mono.defer(() -> (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper
85-
.processRequest(wrapper, wrapper.getParams(), false)));
85+
.processRequest(wrapper, wrapper.getParams(), false,
86+
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders())));
8687
}
8788
else {
8889
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition()));
@@ -96,7 +97,8 @@ public Mono<ResponseEntity<?>> post(ServerWebExchange request,
9697
@RequestBody(required = false) String body) {
9798
FunctionWrapper wrapper = wrapper(request);
9899
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
99-
return (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false);
100+
return (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false,
101+
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
100102
}
101103
else {
102104
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition()));
@@ -110,7 +112,8 @@ public Mono<ResponseEntity<?>> put(ServerWebExchange request,
110112
@RequestBody(required = false) String body) {
111113
FunctionWrapper wrapper = wrapper(request);
112114
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("PUT", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
113-
return (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false);
115+
return (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false,
116+
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
114117
}
115118
else {
116119
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("PUT", wrapper.getFunction().getFunctionDefinition()));
@@ -124,7 +127,8 @@ public Mono<ResponseEntity<?>> delete(ServerWebExchange request,
124127
@RequestBody(required = false) String body) {
125128
FunctionWrapper wrapper = wrapper(request);
126129
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("DELETE", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
127-
return (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false);
130+
return (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false,
131+
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
128132
}
129133
else {
130134
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("DELETE", wrapper.getFunction().getFunctionDefinition()));
@@ -136,7 +140,8 @@ public Mono<ResponseEntity<?>> delete(ServerWebExchange request,
136140
public Publisher<?> postStream(ServerWebExchange request, @RequestBody(required = false) Flux<String> body) {
137141
FunctionWrapper wrapper = wrapper(request);
138142
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
139-
return FunctionWebRequestProcessingHelper.processRequest(wrapper, body, true);
143+
return FunctionWebRequestProcessingHelper.processRequest(wrapper, body, true,
144+
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
140145
}
141146
else {
142147
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition()));
@@ -149,7 +154,8 @@ public Publisher<?> postStream(ServerWebExchange request, @RequestBody(required
149154
public Publisher<?> getStream(ServerWebExchange request) {
150155
FunctionWrapper wrapper = wrapper(request);
151156
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
152-
return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), true);
157+
return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), true,
158+
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
153159
}
154160
else {
155161
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("GET", wrapper.getFunction().getFunctionDefinition()));
@@ -162,7 +168,8 @@ public Publisher<?> getStream(ServerWebExchange request) {
162168
public Mono<ResponseEntity<?>> get(ServerWebExchange request) {
163169
FunctionWrapper wrapper = wrapper(request);
164170
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
165-
return (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), false);
171+
return (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), false,
172+
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
166173
}
167174
else {
168175
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("GET", wrapper.getFunction().getFunctionDefinition()));

spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializer.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.cloud.function.context.catalog.FunctionTypeUtils;
4343
import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper;
4444
import org.springframework.cloud.function.context.config.ContextFunctionCatalogInitializer;
45+
import org.springframework.cloud.function.web.FunctionHttpProperties;
4546
import org.springframework.cloud.function.web.constants.WebRequestConstants;
4647
import org.springframework.cloud.function.web.util.FunctionWebRequestProcessingHelper;
4748
import org.springframework.cloud.function.web.util.FunctionWrapper;
@@ -105,9 +106,10 @@ private void registerWebFluxAutoConfiguration(GenericApplicationContext context)
105106
}
106107

107108
private void registerEndpoint(GenericApplicationContext context) {
109+
context.registerBean(FunctionHttpProperties.class, () -> new FunctionHttpProperties());
108110
context.registerBean(FunctionEndpointFactory.class,
109111
() -> new FunctionEndpointFactory(context.getBean(FunctionProperties.class), context.getBean(FunctionCatalog.class),
110-
context.getEnvironment()));
112+
context.getEnvironment(), context.getBean(FunctionHttpProperties.class)));
111113
RouterFunctionRegister.register(context);
112114
}
113115

@@ -208,14 +210,17 @@ class FunctionEndpointFactory {
208210

209211
private final FunctionProperties functionProperties;
210212

211-
FunctionEndpointFactory(FunctionProperties functionProperties, FunctionCatalog functionCatalog, Environment environment) {
213+
private final FunctionHttpProperties functionHttpProperties;
214+
215+
FunctionEndpointFactory(FunctionProperties functionProperties, FunctionCatalog functionCatalog, Environment environment, FunctionHttpProperties functionHttpProperties) {
212216
String handler = environment.resolvePlaceholders("${function.handler}");
213217
if (handler.startsWith("$")) {
214218
handler = null;
215219
}
216220
this.functionCatalog = functionCatalog;
217221
this.handler = handler;
218222
this.functionProperties = functionProperties;
223+
this.functionHttpProperties = functionHttpProperties;
219224
}
220225

221226
private FunctionInvocationWrapper extract(ServerRequest request) {
@@ -241,7 +246,8 @@ public <T> RouterFunction<?> functionEndpoints() {
241246
: FunctionTypeUtils.getRawType(FunctionTypeUtils.getGenericType(funcWrapper.getOutputType()));
242247
FunctionWrapper wrapper = new FunctionWrapper(funcWrapper);
243248
Mono<ResponseEntity<?>> stream = request.bodyToMono(String.class)
244-
.flatMap(content -> (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, content, false));
249+
.flatMap(content -> (Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, content, false,
250+
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders()));
245251

246252
return stream.flatMap(entity -> {
247253
BodyBuilder builder = status(entity.getStatusCode()).headers(headers -> headers.addAll(entity.getHeaders()));

spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/mvc/FunctionController.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ public Object form(WebRequest request) {
9595
return Mono.from(result).flatMap(body -> Mono.just(builder.body(body)));
9696
}
9797
}
98-
return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getParams(), false);
98+
return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getParams(), false,
99+
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
99100
}
100101
else {
101102
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition()));
@@ -110,7 +111,8 @@ public Mono<ResponseEntity<Publisher<?>>> postStream(WebRequest request,
110111
String argument = StringUtils.hasText(body) ? body : "";
111112
FunctionWrapper wrapper = wrapper(request);
112113
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
113-
return ((Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, argument, true)).map(response -> ResponseEntity.ok()
114+
return ((Mono<ResponseEntity<?>>) FunctionWebRequestProcessingHelper.processRequest(wrapper, argument, true,
115+
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders())).map(response -> ResponseEntity.ok()
114116
.headers(response.getHeaders()).body((Publisher<?>) response.getBody()));
115117
}
116118
else {
@@ -123,7 +125,8 @@ public Mono<ResponseEntity<Publisher<?>>> postStream(WebRequest request,
123125
public Publisher<?> getStream(WebRequest request) {
124126
FunctionWrapper wrapper = wrapper(request);
125127
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
126-
return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), true);
128+
return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), true,
129+
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
127130
}
128131
else {
129132
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("GET", wrapper.getFunction().getFunctionDefinition()));
@@ -136,7 +139,8 @@ public Object post(WebRequest request, @RequestBody(required = false) String bod
136139
FunctionWrapper wrapper = wrapper(request);
137140
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
138141
Assert.isTrue(!wrapper.getFunction().isSupplier(), "'POST' can only be mapped to Function or Consumer");
139-
return FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false);
142+
return FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false,
143+
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
140144
}
141145
else {
142146
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("POST", wrapper.getFunction().getFunctionDefinition()));
@@ -148,7 +152,8 @@ public Object post(WebRequest request, @RequestBody(required = false) String bod
148152
public Object put(WebRequest request, @RequestBody(required = false) String body) {
149153
FunctionWrapper wrapper = wrapper(request);
150154
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("PUT", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
151-
return FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false);
155+
return FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false,
156+
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
152157
}
153158
else {
154159
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("PUT", wrapper.getFunction().getFunctionDefinition()));
@@ -161,7 +166,8 @@ public void delete(WebRequest request, @RequestBody(required = false) String bod
161166
FunctionWrapper wrapper = wrapper(request);
162167
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("DELETE", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
163168
Assert.isTrue(wrapper.getFunction().isConsumer(), "'DELETE' can only be mapped to Consumer");
164-
FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), false);
169+
FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), false,
170+
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
165171
}
166172
else {
167173
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("DELETE", wrapper.getFunction().getFunctionDefinition()));
@@ -173,7 +179,8 @@ public void delete(WebRequest request, @RequestBody(required = false) String bod
173179
public Object get(WebRequest request) {
174180
FunctionWrapper wrapper = wrapper(request);
175181
if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) {
176-
return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), false);
182+
return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), false,
183+
functionHttpProperties.getIgnoredHeaders(), functionHttpProperties.getRequestOnlyHeaders());
177184
}
178185
else {
179186
throw new IllegalArgumentException(FunctionWebRequestProcessingHelper.buildBadMappingErrorMessage("GET", wrapper.getFunction().getFunctionDefinition()));

spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/FunctionExporterAutoConfiguration.java

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
import reactor.core.publisher.Flux;
2323

24-
import org.springframework.beans.factory.annotation.Autowired;
2524
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
2625
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2726
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -31,11 +30,11 @@
3130
import org.springframework.cloud.function.context.FunctionCatalog;
3231
import org.springframework.cloud.function.context.FunctionRegistration;
3332
import org.springframework.cloud.function.context.catalog.FunctionTypeUtils;
33+
import org.springframework.cloud.function.web.FunctionHttpProperties;
3434
import org.springframework.cloud.function.web.source.FunctionExporterAutoConfiguration.SourceActiveCondition;
3535
import org.springframework.context.annotation.Bean;
3636
import org.springframework.context.annotation.Conditional;
3737
import org.springframework.context.annotation.Configuration;
38-
import org.springframework.core.ResolvableType;
3938
import org.springframework.core.env.Environment;
4039
import org.springframework.web.reactive.function.client.WebClient;
4140

@@ -46,14 +45,16 @@
4645
@Configuration(proxyBeanMethods = false)
4746
@ConditionalOnClass(WebClient.class)
4847
@Conditional(SourceActiveCondition.class)
49-
@EnableConfigurationProperties(ExporterProperties.class)
48+
@EnableConfigurationProperties({ExporterProperties.class, FunctionHttpProperties.class})
5049
public class FunctionExporterAutoConfiguration {
5150

52-
private ExporterProperties props;
51+
private final ExporterProperties props;
5352

54-
@Autowired
55-
FunctionExporterAutoConfiguration(ExporterProperties props) {
53+
private final FunctionHttpProperties httpProps;
54+
55+
FunctionExporterAutoConfiguration(ExporterProperties props, FunctionHttpProperties httpProps) {
5656
this.props = props;
57+
this.httpProps = httpProps;
5758
}
5859

5960
@Bean
@@ -66,22 +67,16 @@ public SupplierExporter sourceForwarder(RequestBuilder requestBuilder, Destinati
6667
@Bean
6768
@ConditionalOnProperty(prefix = "spring.cloud.function.web.export.source", name = "url")
6869
public FunctionRegistration<Supplier<Flux<?>>> origin(WebClient.Builder builder) {
69-
HttpSupplier supplier = new HttpSupplier(builder.build(), this.props);
70+
HttpSupplier supplier = new HttpSupplier(builder.build(), this.props, this.httpProps);
7071
FunctionRegistration<Supplier<Flux<?>>> registration = new FunctionRegistration<>(supplier);
71-
Type rawType = ResolvableType.forClassWithGenerics(Supplier.class, this.props.getSource().getType()).getType();
72-
// FunctionType functionType = FunctionType.supplier(this.props.getSource().getType()).wrap(Flux.class);
73-
// FunctionType type = FunctionType.of(rawType);
74-
// if (this.props.getSource().isIncludeHeaders()) {
75-
//// type = type.message();
76-
// }
7772
Type type = FunctionTypeUtils.discoverFunctionTypeFromClass(HttpSupplier.class);
7873
registration = registration.type(type);
7974
return registration;
8075
}
8176

8277
@Bean
8378
public RequestBuilder simpleRequestBuilder(Environment environment) {
84-
SimpleRequestBuilder builder = new SimpleRequestBuilder(environment);
79+
SimpleRequestBuilder builder = new SimpleRequestBuilder(environment, httpProps);
8580
if (this.props.getSink().getUrl() != null) {
8681
builder.setTemplateUrl(this.props.getSink().getUrl());
8782
}

0 commit comments

Comments
 (0)