GH-891 Ensure HTTP Request params are mapped even when body is not present
Resolves #891
This commit is contained in:
@@ -691,12 +691,11 @@ plain text and JSON.
|
||||
|
||||
|===
|
||||
|
||||
As the table above shows the behaviour of the endpoint depends on the method and also the type of incoming request data. When the incoming data
|
||||
is single valued, and the target function is declared as obviously single valued (i.e. not returning a collection or `Flux`), then the response
|
||||
will also contain a single value.
|
||||
As the table above shows the behavior of the endpoint depends on the method and also the type of incoming request data. When the incoming data is single valued, and the target function is declared as obviously single valued (i.e. not returning a collection or `Flux`), then the response will also contain a single value.
|
||||
For multi-valued responses the client can ask for a server-sent event stream by sending `Accept: text/event-stream".
|
||||
|
||||
Functions and consumers that are declared with input and output in `Message<?>` will see the request headers on the input messages, and the output message headers will be converted to HTTP headers.
|
||||
Functions and consumers that are declared with input and output in `Message<?>` will see the request headers as _message headers_, and the output _message headers_ will be converted to HTTP headers.
|
||||
The _payload_ of the Message will be a `body` or empty string if there is no `body` or it is null.
|
||||
|
||||
When POSTing text the response format might be different with Spring Boot 2.0 and older versions, depending on the content negotiation (provide content type and accept headers for the best results).
|
||||
|
||||
@@ -706,8 +705,8 @@ See <<Testing Functional Applications>> to see the details and example on how to
|
||||
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`
|
||||
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 (e.g., `http://localhost:8080/uppercase/foo?name=Bill`)
|
||||
The framework will treat HTTP request parameters similar to the HTTP headers by storing them in the `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<Message<String>, String>`). For convenience we provide `HeaderUtils.HTTP_REQUEST_PARAM` constant.
|
||||
|
||||
=== Function Mapping rules
|
||||
|
||||
@@ -85,27 +85,27 @@ public final class FunctionWebRequestProcessingHelper {
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public static Publisher<?> processRequest(FunctionWrapper wrapper, Object argument, boolean eventStream) {
|
||||
if (argument == null) {
|
||||
argument = "";
|
||||
}
|
||||
FunctionInvocationWrapper function = wrapper.getFunction();
|
||||
|
||||
HttpHeaders headers = wrapper.getHeaders();
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Object input = argument == null ? "" : (argument instanceof Publisher ? Flux.from((Publisher) argument) : inputMessage);
|
||||
|
||||
Object result = function.apply(input);
|
||||
Object result = function.apply(inputMessage);
|
||||
if (function.isConsumer()) {
|
||||
if (result instanceof Publisher) {
|
||||
Mono.from((Publisher) result).subscribe();
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
package org.springframework.cloud.function.web.function;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
@@ -25,7 +30,9 @@ import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.cloud.function.context.FunctionRegistration;
|
||||
import org.springframework.cloud.function.context.FunctionType;
|
||||
@@ -35,6 +42,14 @@ import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.SocketUtils;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -56,6 +71,29 @@ public class FunctionEndpointInitializerTests {
|
||||
public void close() throws Exception {
|
||||
System.clearProperty("server.port");
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@Test
|
||||
public void testEmptyBodyRequestParameters() throws Exception {
|
||||
SpringApplication.run(BeansConfiguration.class);
|
||||
String port = System.getProperty("server.port");
|
||||
TestRestTemplate testRestTemplate = new TestRestTemplate();
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("fname", "Jim");
|
||||
params.put("lname", "Lahey");
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Accept", "application/json");
|
||||
HttpEntity entity = new HttpEntity(headers);
|
||||
|
||||
String urlTemplate = UriComponentsBuilder.fromHttpUrl("http://localhost:" + port + "/nullPayload")
|
||||
.queryParam("fname", "Jim").queryParam("lname", "Lahey").encode().toUriString();
|
||||
|
||||
ResponseEntity<String> response = testRestTemplate.exchange(urlTemplate, HttpMethod.GET, entity, String.class);
|
||||
String res = response.getBody();
|
||||
assertThat(res).contains("Jim");
|
||||
assertThat(res).contains("Lahey");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonExistingFunction() throws Exception {
|
||||
@@ -144,6 +182,17 @@ public class FunctionEndpointInitializerTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@Configuration
|
||||
protected static class BeansConfiguration {
|
||||
@Bean
|
||||
public BiFunction<String, Map<String, Object>, Map<String, Object>> nullPayload() {
|
||||
return (p, h) -> {
|
||||
return h;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SpringBootConfiguration
|
||||
protected static class ApplicationConfiguration
|
||||
|
||||
Reference in New Issue
Block a user