This is an automated email from the ASF dual-hosted git repository.

Croway pushed a commit to branch camel-spring-boot-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel-spring-boot.git


The following commit(s) were added to refs/heads/camel-spring-boot-4.18.x by 
this push:
     new 2a3d211f7a6 ci: fix platform-http failures on 4.18.x
2a3d211f7a6 is described below

commit 2a3d211f7a62fd20c2966e4044dce78ec7b5a7b8
Author: Croway <[email protected]>
AuthorDate: Mon Sep 28 14:44:30 2026 +0200

    ci: fix platform-http failures on 4.18.x
---
 .../springboot/SpringBootPlatformHttpConsumer.java | 27 ++++++++++--
 .../SpringBootPlatformHttpCookiesTest.java         |  2 +-
 .../SpringBootPlatformHttpRequestTimeoutTest.java  | 48 ++++++++++++++++++++--
 3 files changed, 68 insertions(+), 9 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 a3f27529fb4..0a07378d1a5 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
@@ -18,6 +18,7 @@ package org.apache.camel.component.platform.http.springboot;
 
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.Cookie;
@@ -43,11 +44,13 @@ import org.springframework.core.task.AsyncTaskExecutor;
 import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
 import org.springframework.web.bind.annotation.CookieValue;
 import org.springframework.web.bind.annotation.ResponseBody;
+import 
org.springframework.web.context.request.async.AsyncRequestTimeoutException;
 import org.springframework.web.context.request.async.WebAsyncTask;
 
 public class SpringBootPlatformHttpConsumer extends DefaultConsumer implements 
PlatformHttpConsumer, Suspendable, SuspendableService {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(SpringBootPlatformHttpConsumer.class);
+    private static final String REQUEST_TIMED_OUT = 
SpringBootPlatformHttpConsumer.class.getName() + ".requestTimedOut";
 
     private HttpBinding binding;
     private final boolean handleWriteResponseError;
@@ -99,6 +102,8 @@ public class SpringBootPlatformHttpConsumer extends 
DefaultConsumer implements P
      */
     @ResponseBody
     public WebAsyncTask<Void> service(HttpServletRequest request, 
HttpServletResponse response) {
+        AtomicBoolean requestTimedOut = new AtomicBoolean();
+        request.setAttribute(REQUEST_TIMED_OUT, requestTimedOut);
         AsyncTaskExecutor asyncExecutor = (executor instanceof 
AsyncTaskExecutor ate)
                 ? ate
                 : new ConcurrentTaskExecutor(executor);
@@ -110,7 +115,7 @@ public class SpringBootPlatformHttpConsumer extends 
DefaultConsumer implements P
                 // do not leak exception back to caller
                 LOG.warn("Error handling request due to: {}", e.getMessage(), 
e);
                 try {
-                    if (!response.isCommitted()) {
+                    if (!requestTimedOut.get() && !response.isCommitted()) {
                         
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                     }
                 } catch (Exception e1) {
@@ -119,6 +124,14 @@ public class SpringBootPlatformHttpConsumer extends 
DefaultConsumer implements P
             }
             return null;
         });
+        task.onTimeout(() -> {
+            requestTimedOut.set(true);
+            if (!response.isCommitted()) {
+                response.setContentType(null);
+            }
+            // Let Spring MVC resolve the timeout so Boot can render its 
normal error response.
+            throw new AsyncRequestTimeoutException();
+        });
         return task;
     }
 
@@ -130,6 +143,7 @@ public class SpringBootPlatformHttpConsumer extends 
DefaultConsumer implements P
         }
 
         Exchange exchange = createExchange(true);
+        exchange.setProperty(REQUEST_TIMED_OUT, 
request.getAttribute(REQUEST_TIMED_OUT));
         exchange.setPattern(ExchangePattern.InOut);
         HttpHelper.setCharsetFromContentType(request.getContentType(), 
exchange);
         boolean streaming = getEndpoint().isUseStreaming();
@@ -168,7 +182,14 @@ public class SpringBootPlatformHttpConsumer extends 
DefaultConsumer implements P
             if (LOG.isTraceEnabled()) {
                 LOG.trace("Writing res for exchangeId: {}", 
exchange.getExchangeId());
             }
-            binding.writeResponse(exchange, response);
+            AtomicBoolean requestTimedOut = 
exchange.getProperty(REQUEST_TIMED_OUT, AtomicBoolean.class);
+            // Spring cancels the worker before invoking the timeout callback. 
An interrupted route can
+            // finish in that interval, so it must not write over Spring MVC's 
503 error response.
+            boolean cancelled = requestTimedOut != null
+                    && (requestTimedOut.get() || exchange.getException() 
instanceof InterruptedException);
+            if (!cancelled) {
+                binding.writeResponse(exchange, response);
+            }
         } catch (Exception e) {
             writeFailure = true;
             handleFailure(exchange, e);
@@ -249,5 +270,3 @@ public class SpringBootPlatformHttpConsumer extends 
DefaultConsumer implements P
         }
     }
 }
-
-
diff --git 
a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCookiesTest.java
 
b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCookiesTest.java
index 7a67753b5f0..46cfeb3b77b 100644
--- 
a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCookiesTest.java
+++ 
b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCookiesTest.java
@@ -105,7 +105,7 @@ public class SpringBootPlatformHttpCookiesTest {
                             })
                             .setBody().constant("replace");
 
-                    from("platform-http:/echo")
+                    from("platform-http:/echo?returnHttpRequestHeaders=true")
                             .setBody().constant("echo");
                 }
             };
diff --git 
a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpRequestTimeoutTest.java
 
b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpRequestTimeoutTest.java
index 195144a4c20..ffa0602139f 100644
--- 
a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpRequestTimeoutTest.java
+++ 
b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpRequestTimeoutTest.java
@@ -16,6 +16,10 @@
  */
 package org.apache.camel.component.platform.http.springboot;
 
+import java.time.Duration;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.spring.boot.CamelAutoConfiguration;
 import org.apache.camel.test.spring.junit5.CamelSpringBootTest;
@@ -31,6 +35,10 @@ import 
org.springframework.boot.test.web.client.TestRestTemplate;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.HttpStatusCode;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+
+import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
 
 @EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, 
SecurityAutoConfiguration.class})
 @CamelSpringBootTest
@@ -44,22 +52,54 @@ public class SpringBootPlatformHttpRequestTimeoutTest {
     @Autowired
     TestRestTemplate restTemplate;
 
+    @Autowired
+    TestConfiguration testConfiguration;
+
     @Test
-    public void testGetAsync() throws Exception {
-        Assertions.assertThat(restTemplate.getForEntity("/slow-get", 
String.class).getStatusCode())
-                .isEqualTo(HttpStatusCode.valueOf(503));
+    public void testGetAsync() {
+        testConfiguration.routeRelease = new CompletableFuture<>();
+        try {
+            assertTimeoutResponse("/slow-get");
+        } finally {
+            testConfiguration.routeRelease.complete(null);
+        }
+    }
+
+    @Test
+    public void testInterruptedRoute() {
+        testConfiguration.interruptibleRouteRelease = new CountDownLatch(1);
+        try {
+            assertTimeoutResponse("/slow-interruptible-get");
+        } finally {
+            testConfiguration.interruptibleRouteRelease.countDown();
+        }
+    }
+
+    private void assertTimeoutResponse(String path) {
+        ResponseEntity<String> response = 
assertTimeoutPreemptively(Duration.ofSeconds(10),
+                () -> restTemplate.getForEntity(path, String.class));
+        
Assertions.assertThat(response.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(503));
+        Assertions.assertThat(response.getHeaders().getContentType())
+                .satisfies(contentType -> 
Assertions.assertThat(contentType.isCompatibleWith(MediaType.APPLICATION_JSON)).isTrue());
+        Assertions.assertThat(response.getBody()).contains("\"status\":503");
     }
 
     @Configuration
     public static class TestConfiguration {
 
+        private volatile CompletableFuture<Void> routeRelease;
+        private volatile CountDownLatch interruptibleRouteRelease;
+
         @Bean
         public RouteBuilder platformHttpRouteBuilder() {
             return new RouteBuilder() {
                 @Override
                 public void configure() {
                     from("platform-http:/slow-get").id("slow-route")
-                            .process(exchange -> Thread.sleep(1000))
+                            .process(exchange -> routeRelease.join())
+                            .setBody().constant("get");
+                    
from("platform-http:/slow-interruptible-get").id("slow-interruptible-route")
+                            .process(exchange -> 
interruptibleRouteRelease.await())
                             .setBody().constant("get");
                 }
             };

Reply via email to