This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-spring-boot.git
The following commit(s) were added to refs/heads/main by this push: new ed5530a4a5d CAMEL-19182: camel-platform-http-starter - Async processing with Spring Boot (#1199) ed5530a4a5d is described below commit ed5530a4a5d8e29c50e3dc4c3d1a0891ab7a4a1c Author: Ivan Kulaga <kulagaivanandreev...@gmail.com> AuthorDate: Wed Aug 21 19:21:06 2024 +0500 CAMEL-19182: camel-platform-http-starter - Async processing with Spring Boot (#1199) - Spring MVC automatically detects CompletableFuture return type of the handler method and executes asynchronously; @ResponseBody on the handler method to disable attempts to resolve mvc view from a response. - testing async execution with MockMvc - @EnableAutoConfiguration instead of @SpringBootApplication on test classes to not scan another tests' configurations --- .../springboot/SpringBootPlatformHttpConsumer.java | 34 +++--- .../PlatformHttpAsyncRequestHandlingTest.java | 116 +++++++++++++++++++++ .../platform/http/springboot/PlatformHttpBase.java | 26 ++++- .../SpringBootPlatformHttpRestDSLTest.java | 28 +++-- .../springboot/SpringBootPlatformHttpTest.java | 28 +++-- .../src/test/resources/application.properties | 2 + 6 files changed, 203 insertions(+), 31 deletions(-) diff --git a/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpConsumer.java b/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpConsumer.java index fc0ce91d505..8b4ed90f4e1 100644 --- a/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpConsumer.java +++ b/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpConsumer.java @@ -19,6 +19,8 @@ package org.apache.camel.component.platform.http.springboot; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; import org.apache.camel.Exchange; import org.apache.camel.ExchangePattern; import org.apache.camel.Processor; @@ -31,8 +33,7 @@ import org.apache.camel.http.common.HttpHelper; import org.apache.camel.support.DefaultConsumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import java.io.IOException; +import org.springframework.web.bind.annotation.ResponseBody; public class SpringBootPlatformHttpConsumer extends DefaultConsumer implements PlatformHttpConsumer, Suspendable, SuspendableService { @@ -57,21 +58,24 @@ public class SpringBootPlatformHttpConsumer extends DefaultConsumer implements P /** * This method is invoked by Spring Boot when invoking Camel via platform-http */ - public void service(HttpServletRequest request, HttpServletResponse response) { - LOG.trace("Service: {}", request); - try { - handleService(request, response); - } catch (Exception e) { - // do not leak exception back to caller - LOG.warn("Error handling request due to: {}", e.getMessage(), e); + @ResponseBody + public CompletableFuture<Void> service(HttpServletRequest request, HttpServletResponse response) { + return CompletableFuture.runAsync(() -> { + LOG.trace("Service: {}", request); try { - if (!response.isCommitted()) { - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + handleService(request, response); + } catch (Exception e) { + // do not leak exception back to caller + LOG.warn("Error handling request due to: {}", e.getMessage(), e); + try { + if (!response.isCommitted()) { + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } catch (Exception e1) { + // ignore } - } catch (Exception e1) { - // ignore } - } + }); } protected void handleService(HttpServletRequest request, HttpServletResponse response) throws Exception { @@ -94,8 +98,6 @@ public class SpringBootPlatformHttpConsumer extends DefaultConsumer implements P exchange.getIn().setHeader(Exchange.HTTP_PATH, httpPath.substring(contextPath.length())); } - // TODO: async with CompletionStage returned to spring boot? - // we want to handle the UoW try { createUoW(exchange); diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpAsyncRequestHandlingTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpAsyncRequestHandlingTest.java new file mode 100644 index 00000000000..302149febbf --- /dev/null +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpAsyncRequestHandlingTest.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.platform.http.springboot; + +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit5.CamelSpringBootTest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@EnableAutoConfiguration +@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) +@CamelSpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, + SpringBootPlatformHttpRestDSLTest.class, PlatformHttpAsyncRequestHandlingTest.TestConfiguration.class, + PlatformHttpComponentAutoConfiguration.class, SpringBootPlatformHttpAutoConfiguration.class, }) +@AutoConfigureMockMvc +public class PlatformHttpAsyncRequestHandlingTest extends PlatformHttpBase { + + @Autowired + private MockMvc mockMvc; + + @Autowired + CamelContext camelContext; + + private static final String postRouteId = "PlatformHttpAsyncRequestHandlingTest_mypost"; + + private static final String getRouteId = "PlatformHttpAsyncRequestHandlingTest_myget"; + + @Test + public void testGetAsync() throws Exception { + waitUntilRouteIsStarted(1, getGetRouteId()); + + MvcResult mvcResult = mockMvc.perform(get("/myget")) + .andExpect(status().isOk()) + .andExpect(request().asyncStarted()) + .andReturn(); + + mockMvc.perform(asyncDispatch(mvcResult)) + .andExpect(status().isOk()) + .andExpect(content().string("get")); + } + + @Test + public void testPostAsync() throws Exception { + waitUntilRouteIsStarted(1, getPostRouteId()); + + MvcResult mvcResult = mockMvc.perform(post("/mypost").content("hello")) + .andExpect(status().isOk()) + .andExpect(request().asyncStarted()) + .andReturn(); + + mockMvc.perform(asyncDispatch(mvcResult)) + .andExpect(status().isOk()) + .andExpect(content().string("HELLO")); + } + + // ************************************* + // Config + // ************************************* + @Configuration + public static class TestConfiguration { + + @Bean + public RouteBuilder servletPlatformHttpRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("platform-http:/myget").id(getRouteId).setBody().constant("get"); + from("platform-http:/mypost").id(postRouteId).transform().body(String.class, b -> b.toUpperCase()); + } + }; + } + } + + @Override + protected String getPostRouteId() { + return postRouteId; + } + + @Override + protected String getGetRouteId() { + return getRouteId; + } +} diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpBase.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpBase.java index 387a8bf76de..dd3bb7ec2c0 100644 --- a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpBase.java +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpBase.java @@ -16,24 +16,46 @@ */ package org.apache.camel.component.platform.http.springboot; -import org.junit.jupiter.api.Test; - +import java.util.concurrent.TimeUnit; +import org.apache.camel.CamelContext; +import org.apache.camel.ServiceStatus; import org.assertj.core.api.Assertions; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.web.client.TestRestTemplate; +import static org.junit.jupiter.api.Assertions.assertEquals; + abstract class PlatformHttpBase { @Autowired private TestRestTemplate restTemplate; + @Autowired + CamelContext camelContext; + @Test public void testGet() { + waitUntilRouteIsStarted(1, getGetRouteId()); + Assertions.assertThat(restTemplate.getForEntity("/myget", String.class).getStatusCodeValue()).isEqualTo(200); } @Test public void testPost() { + waitUntilRouteIsStarted(1, getPostRouteId()); + Assertions.assertThat(restTemplate.postForEntity("/mypost", "test", String.class).getBody()).isEqualTo("TEST"); } + + protected void waitUntilRouteIsStarted(int atMostSeconds, String routeId) { + Awaitility.await().atMost(atMostSeconds, TimeUnit.SECONDS).untilAsserted(() -> + assertEquals(ServiceStatus.Started, camelContext.getRouteController().getRouteStatus(routeId)) + ); + } + + protected abstract String getPostRouteId(); + + protected abstract String getGetRouteId(); } diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpRestDSLTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpRestDSLTest.java index 6e2ad3f8673..b6761b9877d 100644 --- a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpRestDSLTest.java +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpRestDSLTest.java @@ -19,21 +19,25 @@ package org.apache.camel.component.platform.http.springboot; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.spring.boot.CamelAutoConfiguration; import org.apache.camel.test.spring.junit5.CamelSpringBootTest; - -import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; -@SpringBootApplication -@DirtiesContext +@EnableAutoConfiguration +@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) @CamelSpringBootTest @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, SpringBootPlatformHttpRestDSLTest.class, SpringBootPlatformHttpRestDSLTest.TestConfiguration.class, PlatformHttpComponentAutoConfiguration.class, SpringBootPlatformHttpAutoConfiguration.class, }) public class SpringBootPlatformHttpRestDSLTest extends PlatformHttpBase { + private static final String postRouteId = "SpringBootPlatformHttpRestDSLTest_mypost"; + + private static final String getRouteId = "SpringBootPlatformHttpRestDSLTest_myget"; + // ************************************* // Config // ************************************* @@ -44,8 +48,10 @@ public class SpringBootPlatformHttpRestDSLTest extends PlatformHttpBase { public RouteBuilder springBootPlatformHttpRestDSLRouteBuilder() { return new RouteBuilder() { @Override - public void configure() throws Exception { - rest().get("myget").to("direct:get").post("mypost").to("direct:post"); + public void configure() { + rest() + .get("myget").id(getRouteId).to("direct:get") + .post("mypost").id(postRouteId).to("direct:post"); from("direct:post").transform().body(String.class, b -> b.toUpperCase()); from("direct:get").setBody().constant("get"); @@ -53,4 +59,14 @@ public class SpringBootPlatformHttpRestDSLTest extends PlatformHttpBase { }; } } + + @Override + protected String getPostRouteId() { + return postRouteId; + } + + @Override + protected String getGetRouteId() { + return getRouteId; + } } diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpTest.java index 40653c77c77..77355b21277 100644 --- a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpTest.java +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpTest.java @@ -19,21 +19,25 @@ package org.apache.camel.component.platform.http.springboot; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.spring.boot.CamelAutoConfiguration; import org.apache.camel.test.spring.junit5.CamelSpringBootTest; - -import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; -@SpringBootApplication -@DirtiesContext +@EnableAutoConfiguration +@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) @CamelSpringBootTest @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, SpringBootPlatformHttpTest.class, SpringBootPlatformHttpTest.TestConfiguration.class, PlatformHttpComponentAutoConfiguration.class, SpringBootPlatformHttpAutoConfiguration.class }) public class SpringBootPlatformHttpTest extends PlatformHttpBase { + private static final String postRouteId = "SpringBootPlatformHttpTest_mypost"; + + private static final String getRouteId = "SpringBootPlatformHttpTest_myget"; + // ************************************* // Config // ************************************* @@ -44,11 +48,21 @@ public class SpringBootPlatformHttpTest extends PlatformHttpBase { public RouteBuilder servletPlatformHttpRouteBuilder() { return new RouteBuilder() { @Override - public void configure() throws Exception { - from("platform-http:/myget").setBody().constant("get"); - from("platform-http:/mypost").transform().body(String.class, b -> b.toUpperCase()); + public void configure() { + from("platform-http:/myget").id(postRouteId).setBody().constant("get"); + from("platform-http:/mypost").id(getRouteId).transform().body(String.class, b -> b.toUpperCase()); } }; } } + + @Override + protected String getPostRouteId() { + return postRouteId; + } + + @Override + protected String getGetRouteId() { + return getRouteId; + } } diff --git a/components-starter/camel-platform-http-starter/src/test/resources/application.properties b/components-starter/camel-platform-http-starter/src/test/resources/application.properties new file mode 100644 index 00000000000..2a1a48d10d4 --- /dev/null +++ b/components-starter/camel-platform-http-starter/src/test/resources/application.properties @@ -0,0 +1,2 @@ +#logging.level.org.springframework.web: DEBUG +#logging.level.org.springframework.boot.web: DEBUG \ No newline at end of file