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

davsclaus pushed a commit to branch camel-spring-boot-4.8.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.8.x by 
this push:
     new 4ea389d46db CAMEL-21300: camel-platform-http - Consumer should have 
option to control if writing response failing should cause Exchange to fail
4ea389d46db is described below

commit 4ea389d46db546946c6e34b0daf9e09235c9fa7e
Author: Claus Ibsen <claus.ib...@gmail.com>
AuthorDate: Wed Oct 2 12:37:43 2024 +0200

    CAMEL-21300: camel-platform-http - Consumer should have option to control 
if writing response failing should cause Exchange to fail
---
 .../http/springboot/PlatformHttpMessage.java       |   6 +-
 .../springboot/SpringBootPlatformHttpConsumer.java |  56 ++++++---
 .../platform/http/springboot/PlatformHttpBase.java |   6 +-
 ...SpringBootPlatformHttpHandleWriteErrorTest.java | 139 +++++++++++++++++++++
 4 files changed, 185 insertions(+), 22 deletions(-)

diff --git 
a/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/PlatformHttpMessage.java
 
b/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/PlatformHttpMessage.java
index b3471309b35..4e8abf92fe3 100644
--- 
a/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/PlatformHttpMessage.java
+++ 
b/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/PlatformHttpMessage.java
@@ -98,7 +98,11 @@ public class PlatformHttpMessage extends DefaultMessage {
     }
 
     public PlatformHttpMessage newInstance() {
-        return new PlatformHttpMessage(this.request, this.response, 
this.getExchange(), this.binding, this.requestRead);
+        PlatformHttpMessage answer = new PlatformHttpMessage(this.request, 
this.response, this.getExchange(), this.binding, this.requestRead);
+        if (answer.camelContext == null) {
+            answer.setCamelContext(this.camelContext);
+        }
+        return answer;
     }
 
     public String toString() {
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 8b4ed90f4e1..8a312416abf 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,7 +19,6 @@ 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;
@@ -29,6 +28,7 @@ import org.apache.camel.SuspendableService;
 import org.apache.camel.component.platform.http.PlatformHttpEndpoint;
 import org.apache.camel.component.platform.http.spi.PlatformHttpConsumer;
 import org.apache.camel.http.common.DefaultHttpBinding;
+import org.apache.camel.http.common.HttpBinding;
 import org.apache.camel.http.common.HttpHelper;
 import org.apache.camel.support.DefaultConsumer;
 import org.slf4j.Logger;
@@ -39,7 +39,8 @@ public class SpringBootPlatformHttpConsumer extends 
DefaultConsumer implements P
 
     private static final Logger LOG = 
LoggerFactory.getLogger(SpringBootPlatformHttpConsumer.class);
 
-    private final DefaultHttpBinding binding;
+    private HttpBinding binding;
+    private final boolean handleWriteResponseError;
 
     public SpringBootPlatformHttpConsumer(PlatformHttpEndpoint endpoint, 
Processor processor) {
         super(endpoint, processor);
@@ -48,6 +49,14 @@ public class SpringBootPlatformHttpConsumer extends 
DefaultConsumer implements P
         this.binding.setMuteException(endpoint.isMuteException());
         
this.binding.setFileNameExtWhitelist(endpoint.getFileNameExtWhitelist());
         this.binding.setUseReaderForPayload(!endpoint.isUseStreaming());
+        this.handleWriteResponseError = endpoint.isHandleWriteResponseError();
+    }
+
+    /**
+     * Used for testing purposes
+     */
+    void setBinding(HttpBinding binding) {
+        this.binding = binding;
     }
 
     @Override
@@ -112,36 +121,47 @@ public class SpringBootPlatformHttpConsumer extends 
DefaultConsumer implements P
         } catch (Exception e) {
             exchange.setException(e);
         } finally {
-            afterProcess(response, exchange, true);
+            afterProcess(response, exchange);
         }
     }
 
-    protected void afterProcess(HttpServletResponse response, Exchange 
exchange, boolean rethrow)
-            throws IOException, ServletException {
+    protected void afterProcess(HttpServletResponse response, Exchange 
exchange) throws Exception {
+        boolean writeFailure = false;
         try {
             // now lets output to the res
             if (LOG.isTraceEnabled()) {
                 LOG.trace("Writing res for exchangeId: {}", 
exchange.getExchangeId());
             }
             binding.writeResponse(exchange, response);
-        } catch (IOException e) {
-            LOG.error("Error processing request", e);
-            if (rethrow) {
-                throw e;
-            } else {
-                exchange.setException(e);
-            }
         } catch (Exception e) {
-            LOG.error("Error processing request", e);
-            if (rethrow) {
-                throw new ServletException(e);
-            } else {
-                exchange.setException(e);
-            }
+            writeFailure = true;
+            handleFailure(exchange, e);
         } finally {
             doneUoW(exchange);
             releaseExchange(exchange, false);
         }
+        try {
+            if (writeFailure && !response.isCommitted()) {
+                
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            }
+        } catch (Exception e) {
+            // ignore
+        }
+
+    }
+
+    private void handleFailure(Exchange exchange, Throwable failure) {
+        getExceptionHandler().handleException(
+                "Failed writing HTTP response url: " + getEndpoint().getPath() 
+ " due to: " + failure.getMessage(),
+                failure);
+        if (handleWriteResponseError) {
+            Exception existing = exchange.getException();
+            if (existing != null) {
+                failure.addSuppressed(existing);
+            }
+            exchange.setProperty(Exchange.EXCEPTION_CAUGHT, failure);
+            exchange.setException(failure);
+        }
     }
 
 }
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 dd3bb7ec2c0..79d5cb321d2 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
@@ -30,20 +30,20 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 abstract class PlatformHttpBase {
 
     @Autowired
-    private TestRestTemplate restTemplate;
+    TestRestTemplate restTemplate;
 
     @Autowired
     CamelContext camelContext;
 
     @Test
-    public void testGet() {
+    public void testGet() throws Exception {
         waitUntilRouteIsStarted(1, getGetRouteId());
 
         Assertions.assertThat(restTemplate.getForEntity("/myget", 
String.class).getStatusCodeValue()).isEqualTo(200);
     }
 
     @Test
-    public void testPost() {
+    public void testPost() throws Exception {
         waitUntilRouteIsStarted(1, getPostRouteId());
 
         Assertions.assertThat(restTemplate.postForEntity("/mypost", "test", 
String.class).getBody()).isEqualTo("TEST");
diff --git 
a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpHandleWriteErrorTest.java
 
b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpHandleWriteErrorTest.java
new file mode 100644
index 00000000000..37a3cb384bb
--- /dev/null
+++ 
b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpHandleWriteErrorTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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 jakarta.servlet.http.HttpServletResponse;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.component.platform.http.PlatformHttpEndpoint;
+import org.apache.camel.component.platform.http.spi.PlatformHttpConsumer;
+import org.apache.camel.component.platform.http.spi.PlatformHttpEngine;
+import org.apache.camel.http.common.DefaultHttpBinding;
+import org.apache.camel.spring.boot.CamelAutoConfiguration;
+import org.apache.camel.test.spring.junit5.CamelSpringBootTest;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+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.core.env.Environment;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.ClassMode;
+
+import java.io.IOException;
+
+@EnableAutoConfiguration
+@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
+@CamelSpringBootTest
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, 
classes = { CamelAutoConfiguration.class,
+        SpringBootPlatformHttpHandleWriteErrorTest.class, 
SpringBootPlatformHttpHandleWriteErrorTest.TestConfiguration.class,
+        PlatformHttpComponentAutoConfiguration.class, 
SpringBootPlatformHttpAutoConfiguration.class })
+public class SpringBootPlatformHttpHandleWriteErrorTest extends 
PlatformHttpBase {
+
+    private static final String postRouteId = 
"SpringBootPlatformHttpHandleWriteErrorTest_mypost";
+    private static final String getRouteId = 
"SpringBootPlatformHttpHandleWriteErrorTest_myget";
+
+    @Test
+    @Override
+    public void testGet() throws Exception {
+        MockEndpoint me = camelContext.getEndpoint("mock:failure", 
MockEndpoint.class);
+        me.expectedMessageCount(0);
+
+        super.testGet();
+
+        me.assertIsSatisfied();
+    }
+
+    @Test
+    @Override
+    public void testPost() throws Exception {
+        MockEndpoint me = camelContext.getEndpoint("mock:failure", 
MockEndpoint.class);
+        me.expectedMessageCount(1);
+
+        waitUntilRouteIsStarted(1, getPostRouteId());
+        Assertions.assertThat(restTemplate.postForEntity("/mypost", "test", 
String.class).getStatusCode().value()).isEqualTo(500);
+
+        me.assertIsSatisfied();
+    }
+
+    // *************************************
+    // Config
+    // *************************************
+    @Configuration
+    public static class TestConfiguration {
+
+        @Bean(name = "platform-http-engine")
+        public PlatformHttpEngine myHttpEngine(Environment env) {
+            int port = Integer.parseInt(env.getProperty("server.port", 
"8080"));
+            return new MyEngine(port);
+        }
+
+        @Bean
+        public RouteBuilder servletPlatformHttpRouteBuilder() {
+            return new RouteBuilder() {
+                @Override
+                public void configure() {
+                    
onCompletion().onFailureOnly().to("log:failure").to("mock:failure");
+
+                    
from("platform-http:/myget").id(getRouteId).setBody().constant("get");
+                    
from("platform-http:/mypost?handleWriteResponseError=true").id(postRouteId).transform().body(String.class,
 b -> b.toUpperCase());
+                }
+            };
+        }
+    }
+
+    @Override
+    protected String getPostRouteId() {
+        return postRouteId;
+    }
+
+    @Override
+    protected String getGetRouteId() {
+        return getRouteId;
+    }
+
+    private static class MyErrorBinding extends DefaultHttpBinding {
+
+        @Override
+        public void writeResponse(Exchange exchange, HttpServletResponse 
response) throws IOException {
+            // force an exception during writing response to simulate error at 
that point
+            String uri = exchange.getMessage().getHeader(Exchange.HTTP_URI, 
String.class);
+            if ("/mypost".equals(uri)) {
+                throw new IOException("Forced error");
+            } else {
+                super.writeResponse(exchange, response);
+            }
+        }
+    }
+
+    private static class MyEngine extends SpringBootPlatformHttpEngine {
+
+        public MyEngine(int port) {
+            super(port);
+        }
+
+        @Override
+        public PlatformHttpConsumer createConsumer(PlatformHttpEndpoint 
endpoint, Processor processor) {
+            SpringBootPlatformHttpConsumer answer = new 
SpringBootPlatformHttpConsumer(endpoint, processor);
+            answer.setBinding(new MyErrorBinding());
+            return answer;
+        }
+    }
+}

Reply via email to