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");
}
};