diff --git a/docs/src/main/asciidoc/spring-cloud-function.adoc b/docs/src/main/asciidoc/spring-cloud-function.adoc index 2eb40ec54..f53e497aa 100644 --- a/docs/src/main/asciidoc/spring-cloud-function.adoc +++ b/docs/src/main/asciidoc/spring-cloud-function.adoc @@ -650,6 +650,13 @@ When POSTing text the response format might be different with Spring Boot 2.0 an See <> to see the details and example on how to test such application. +==== HTTP Request Parameters +As you have noticed from the previous table, you can pass an argument to a function as path variable (i.e., `/{function}/{item}`). +For example, `http://localhost:8080/uppercase/foo` will result in calling `uppercase` function with its input parameter being `foo`. + +While this is the recommended approach and the one that fits most use cases cases, there are times when you have to deal with HTTP request parameters. +The framework will treat HTTP request parameters similar to the HTTP headers by storing them in `Message` headers under the header key `http_request_param` +with its value being a `Map` of request parameters, so in order to access them your function input signature should accept `Message` type (e.g., `Function, String>`). For convenience we provide `HeaderUtils.HTTP_REQUEST_PARAM` constant. === Function Mapping rules 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 c427fdc13..3d589517c 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 @@ -89,7 +89,15 @@ public final class FunctionWebRequestProcessingHelper { HttpHeaders headers = wrapper.getHeaders(); - Message inputMessage = argument == null ? null : MessageBuilder.withPayload(argument).copyHeaders(headers.toSingleValueMap()).build(); + Message inputMessage = null; + + if (argument != null) { + MessageBuilder builder = MessageBuilder.withPayload(argument); + if (!CollectionUtils.isEmpty(wrapper.getParams())) { + builder = builder.setHeader(HeaderUtils.HTTP_REQUEST_PARAM, wrapper.getParams().toSingleValueMap()); + } + inputMessage = builder.copyHeaders(headers.toSingleValueMap()).build(); + } if (function.isRoutingFunction()) { function.setSkipOutputConversion(true); 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 50e445dbe..f16c1afb2 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 @@ -31,6 +31,11 @@ import org.springframework.messaging.MessageHeaders; */ public final class HeaderUtils { + /** + * Message Header name which contains HTTP request parameters. + */ + public static final String HTTP_REQUEST_PARAM = "http_request_param"; + private static HttpHeaders IGNORED = new HttpHeaders(); private static HttpHeaders REQUEST_ONLY = new HttpHeaders(); diff --git a/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/mvc/PrefixTests.java b/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/mvc/PrefixTests.java index 26f4179c7..d8b264cb9 100644 --- a/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/mvc/PrefixTests.java +++ b/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/mvc/PrefixTests.java @@ -17,6 +17,8 @@ package org.springframework.cloud.function.web.mvc; import java.net.URI; +import java.util.Map; +import java.util.function.Function; import java.util.function.Supplier; import org.junit.jupiter.api.Test; @@ -29,10 +31,12 @@ import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.cloud.function.web.RestApplication; import org.springframework.cloud.function.web.mvc.PrefixTests.TestConfiguration; +import org.springframework.cloud.function.web.util.HeaderUtils; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpStatus; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; +import org.springframework.messaging.Message; import org.springframework.test.context.ContextConfiguration; import static org.assertj.core.api.Assertions.assertThat; @@ -65,6 +69,15 @@ public class PrefixTests { assertThat(result.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); } + + @Test + public void uppercase() throws Exception { + ResponseEntity result = this.rest.exchange( + RequestEntity.get(new URI("/functions/uppercase/foo?nome=Doe&prenome=John")).build(), String.class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(result.getBody()).isEqualTo("[\"foo\",\"bar\"]"); + } + @EnableAutoConfiguration @org.springframework.boot.test.context.TestConfiguration protected static class TestConfiguration { @@ -74,6 +87,17 @@ public class PrefixTests { return () -> Flux.fromArray(new String[] { "foo", "bar" }); } + @Bean + public Function, String[]> uppercase() { + return message -> { + assertThat(message.getPayload().equals("foo")); + Map httpParam = (Map) message.getHeaders().get(HeaderUtils.HTTP_REQUEST_PARAM); + assertThat(httpParam.get("nome")).isEqualTo("Doe"); + assertThat(httpParam.get("prenome")).isEqualTo("John"); + return new String[] { "foo", "bar" }; + }; + } + } }