diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionController.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionController.java index 42f64a745..239ac546e 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionController.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionController.java @@ -62,7 +62,7 @@ public class FunctionController { @ResponseBody public Mono> form(ServerWebExchange request) { FunctionWrapper wrapper = wrapper(request); - if (FunctionWebRequestProcessingHelper.isValidFunction("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { + if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { return request.getFormData().doOnSuccess(params -> wrapper.getParams().addAll(params)) .then(Mono.defer(() -> (Mono>) FunctionWebRequestProcessingHelper .processRequest(wrapper, wrapper.getParams(), false))); @@ -78,7 +78,7 @@ public class FunctionController { @ResponseBody public Mono> multipart(ServerWebExchange request) { FunctionWrapper wrapper = wrapper(request); - if (FunctionWebRequestProcessingHelper.isValidFunction("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { + if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { return request.getMultipartData() .doOnSuccess(params -> wrapper.getParams().addAll(multi(params))) .then(Mono.defer(() -> (Mono>) FunctionWebRequestProcessingHelper @@ -95,7 +95,7 @@ public class FunctionController { public Mono> post(ServerWebExchange request, @RequestBody(required = false) String body) { FunctionWrapper wrapper = wrapper(request); - if (FunctionWebRequestProcessingHelper.isValidFunction("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { + if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { return (Mono>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false); } else { @@ -109,7 +109,7 @@ public class FunctionController { public Mono> put(ServerWebExchange request, @RequestBody(required = false) String body) { FunctionWrapper wrapper = wrapper(request); - if (FunctionWebRequestProcessingHelper.isValidFunction("PUT", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { + if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("PUT", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { return (Mono>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false); } else { @@ -123,7 +123,7 @@ public class FunctionController { public Mono> delete(ServerWebExchange request, @RequestBody(required = false) String body) { FunctionWrapper wrapper = wrapper(request); - if (FunctionWebRequestProcessingHelper.isValidFunction("DELETE", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { + if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("DELETE", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { return (Mono>) FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false); } else { @@ -135,7 +135,7 @@ public class FunctionController { @ResponseBody public Publisher postStream(ServerWebExchange request, @RequestBody(required = false) Flux body) { FunctionWrapper wrapper = wrapper(request); - if (FunctionWebRequestProcessingHelper.isValidFunction("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { + if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { return FunctionWebRequestProcessingHelper.processRequest(wrapper, body, true); } else { @@ -148,7 +148,7 @@ public class FunctionController { @ResponseBody public Publisher getStream(ServerWebExchange request) { FunctionWrapper wrapper = wrapper(request); - if (FunctionWebRequestProcessingHelper.isValidFunction("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { + if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), true); } else { @@ -161,7 +161,7 @@ public class FunctionController { @ResponseBody public Mono> get(ServerWebExchange request) { FunctionWrapper wrapper = wrapper(request); - if (FunctionWebRequestProcessingHelper.isValidFunction("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { + if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { return (Mono>) FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), false); } else { diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/mvc/FunctionController.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/mvc/FunctionController.java index 7dddb35b9..0e5977e7b 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/mvc/FunctionController.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/mvc/FunctionController.java @@ -75,7 +75,7 @@ public class FunctionController { @ResponseBody public Object form(WebRequest request) { FunctionWrapper wrapper = wrapper(request); - if (FunctionWebRequestProcessingHelper.isValidFunction("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { + if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { if (((ServletWebRequest) request).getRequest() instanceof StandardMultipartHttpServletRequest) { MultiValueMap multiFileMap = ((StandardMultipartHttpServletRequest) ((ServletWebRequest) request) .getRequest()).getMultiFileMap(); @@ -109,7 +109,7 @@ public class FunctionController { @RequestBody(required = false) String body) { String argument = StringUtils.hasText(body) ? body : ""; FunctionWrapper wrapper = wrapper(request); - if (FunctionWebRequestProcessingHelper.isValidFunction("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { + if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("POST", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { return ((Mono>) FunctionWebRequestProcessingHelper.processRequest(wrapper, argument, true)).map(response -> ResponseEntity.ok() .headers(response.getHeaders()).body((Publisher) response.getBody())); } @@ -122,7 +122,7 @@ public class FunctionController { @ResponseBody public Publisher getStream(WebRequest request) { FunctionWrapper wrapper = wrapper(request); - if (FunctionWebRequestProcessingHelper.isValidFunction("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { + if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), true); } else { @@ -134,7 +134,8 @@ public class FunctionController { @ResponseBody public Object post(WebRequest request, @RequestBody(required = false) String body) { FunctionWrapper wrapper = wrapper(request); - if (FunctionWebRequestProcessingHelper.isValidFunction("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"); return FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false); } else { @@ -146,7 +147,7 @@ public class FunctionController { @ResponseBody public Object put(WebRequest request, @RequestBody(required = false) String body) { FunctionWrapper wrapper = wrapper(request); - if (FunctionWebRequestProcessingHelper.isValidFunction("PUT", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { + if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("PUT", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { return FunctionWebRequestProcessingHelper.processRequest(wrapper, body, false); } else { @@ -155,10 +156,10 @@ public class FunctionController { } @DeleteMapping(path = "/**") - @ResponseStatus(value = HttpStatus.NO_CONTENT) + @ResponseStatus(HttpStatus.NO_CONTENT) public void delete(WebRequest request, @RequestBody(required = false) String body) { FunctionWrapper wrapper = wrapper(request); - if (FunctionWebRequestProcessingHelper.isValidFunction("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"); FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), false); } @@ -171,7 +172,7 @@ public class FunctionController { @ResponseBody public Object get(WebRequest request) { FunctionWrapper wrapper = wrapper(request); - if (FunctionWebRequestProcessingHelper.isValidFunction("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { + if (FunctionWebRequestProcessingHelper.isFunctionValidForMethod("GET", wrapper.getFunction().getFunctionDefinition(), this.functionHttpProperties)) { return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getArgument(), false); } else { 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 43af8d426..2f92cb1b8 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 @@ -71,7 +71,7 @@ public final class FunctionWebRequestProcessingHelper { return postProcessResult(result, isMessage); } - public static boolean isValidFunction(String httpMethod, String functionDefinition, FunctionHttpProperties functionHttpProperties) { + public static boolean isFunctionValidForMethod(String httpMethod, String functionDefinition, FunctionHttpProperties functionHttpProperties) { String functionDefinitions = null; switch (httpMethod) { case "GET": @@ -92,7 +92,7 @@ public final class FunctionWebRequestProcessingHelper { if (StringUtils.hasText(functionDefinitions)) { return Arrays.asList(functionDefinitions.split(";")).contains(functionDefinition); } - return false; + return true; } public static String buildBadMappingErrorMessage(String httpMethod, String functionDefinition) { @@ -135,7 +135,7 @@ public final class FunctionWebRequestProcessingHelper { Mono.from((Publisher) result).subscribe(); } return "DELETE".equals(wrapper.getMethod()) ? - Mono.empty() : Mono.just(ResponseEntity.ok().headers(HeaderUtils.sanitize(headers)).build()); + Mono.empty() : Mono.just(ResponseEntity.accepted().headers(HeaderUtils.sanitize(headers)).build()); } BodyBuilder responseOkBuilder = ResponseEntity.ok().headers(HeaderUtils.sanitize(headers)); diff --git a/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/mvc/GeneralIntegrationTests.java b/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/mvc/GeneralIntegrationTests.java index c3af4c333..da20059df 100644 --- a/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/mvc/GeneralIntegrationTests.java +++ b/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/mvc/GeneralIntegrationTests.java @@ -29,6 +29,7 @@ import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; @@ -42,22 +43,22 @@ public class GeneralIntegrationTests { @Test public void testMappedAndUnmappedDeleteFunction() throws Exception { ApplicationContext context = SpringApplication.run(MultipleConsumerConfiguration.class, "--server.port=0", - "--spring.cloud.function.http.DELETE=delete2;deleteFunction|delete1"); + "--spring.cloud.function.http.DELETE=consumer2;supplier;function|consumer1"); String port = context.getEnvironment().getProperty("local.server.port"); TestRestTemplate template = new TestRestTemplate(); ResponseEntity result = template.exchange( - RequestEntity.delete(new URI("http://localhost:" + port + "/delete1")) + RequestEntity.delete(new URI("http://localhost:" + port + "/consumer1")) .build(), Void.class); assertThat(result.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); result = template.exchange( - RequestEntity.delete(new URI("http://localhost:" + port + "/delete2")) + RequestEntity.delete(new URI("http://localhost:" + port + "/consumer2")) .build(), Void.class); assertThat(result.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); result = template.exchange( - RequestEntity.delete(new URI("http://localhost:" + port + "/deleteFunction,delete1")) + RequestEntity.delete(new URI("http://localhost:" + port + "/function,consumer1")) .build(), Void.class); assertThat(result.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); @@ -67,22 +68,59 @@ public class GeneralIntegrationTests { assertThat(result.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); } + @Test + public void testMappedAndUnmappedPostPutFunction() throws Exception { + ApplicationContext context = SpringApplication.run(MultipleConsumerConfiguration.class, "--server.port=0", + "--spring.cloud.function.http.POST=consumer2;function;supplier;function|consumer1"); + String port = context.getEnvironment().getProperty("local.server.port"); + TestRestTemplate template = new TestRestTemplate(); + + ResponseEntity result = template.exchange(RequestEntity + .post(new URI("http://localhost:" + port + "/consumer1")).contentType(MediaType.APPLICATION_JSON) + .body("[\"foo\",\"bar\"]"), String.class); + + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); + + result = template.exchange(RequestEntity + .post(new URI("http://localhost:" + port + "/consumer2")).contentType(MediaType.APPLICATION_JSON) + .body("[\"foo\",\"bar\"]"), String.class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED); + assertThat(result.getBody()).isNull(); + + result = template.exchange(RequestEntity + .post(new URI("http://localhost:" + port + "/function,consumer1")).contentType(MediaType.APPLICATION_JSON) + .body("[\"foo\",\"bar\"]"), String.class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED); + assertThat(result.getBody()).isNull(); + + result = template.exchange(RequestEntity + .post(new URI("http://localhost:" + port + "/function")).contentType(MediaType.APPLICATION_JSON) + .body("[\"foo\",\"bar\"]"), String.class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(result.getBody()).isEqualTo("[\"foo\",\"bar\"]"); + + result = template.exchange(RequestEntity + .post(new URI("http://localhost:" + port + "/supplier")).contentType(MediaType.APPLICATION_JSON) + .body("[\"foo\",\"bar\"]"), String.class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); + } + @EnableAutoConfiguration protected static class MultipleConsumerConfiguration { @Bean - public Consumer delete1() { - return v -> {}; + public Consumer consumer1() { + return v -> { }; } @Bean - public Consumer delete2() { - return v -> {}; + public Consumer consumer2() { + return v -> { }; } @Bean - public Function deleteFunction() { + public Function function() { return v -> v; } diff --git a/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/mvc/HttpDeleteIntegrationTests.java b/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/mvc/HttpDeleteIntegrationTests.java index f27de5431..834edd2da 100644 --- a/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/mvc/HttpDeleteIntegrationTests.java +++ b/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/mvc/HttpDeleteIntegrationTests.java @@ -16,8 +16,6 @@ package org.springframework.cloud.function.web.mvc; -import static org.assertj.core.api.Assertions.assertThat; - import java.net.URI; import java.util.ArrayList; import java.util.List; @@ -27,6 +25,7 @@ import java.util.function.Function; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -35,7 +34,6 @@ import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.cloud.function.web.RestApplication; import org.springframework.cloud.function.web.mvc.HttpDeleteIntegrationTests.ApplicationConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpStatus; @@ -43,7 +41,8 @@ import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.messaging.Message; import org.springframework.test.context.ContextConfiguration; -import org.springframework.util.MimeType; + +import static org.assertj.core.api.Assertions.assertThat; /** * @author Oleg Zhurakousky