Hello,

I'm facing a weird behavior on the Camel-HTTP component (4.2.2) when I want
to use the "disableStreamCache=true" configuration specified in this page :
https://camel.apache.org/components/4.4.x/http-component.html.
When this option is set to true, the stream is consumed first by the Apache
HTTP Client, and then, when Camel tries to convert the InputStream to
CacheOutputStream object by copying the data, it raises a
"StreamAlreadyClosed" exception.

I even tried to get the body in a processor, to convert it into another
object like String.class etc... So, I tried to go deeply in the code to
figure out this error. Please find below my analysis.

When I make an HTTP call, the execution goes through the
org.apache.hc.client5.http.impl.classic.CloseableHttpClient#execute(HttpHost,
ClassicHttpRequest, HttpContext, HttpClientResponseHandler) method. Here,
it calls the handler defined by Camel in
org.apache.camel.component.http.HttpProducer#process(Exchange) - line 248.
In this handler, because I set up the option "disableStreamCache=true" the
handler sets the exchange body as the InputStream provided by the HTTP
Client. So, when it goes back to the execute method, the Apache HTTP Client
consumes the stream (i.e ; he closes it).
This is where the problem starts. In the exchange, I have a closed stream
that can't be used inside my route. For example, if I try to log the body,
it raises the exception pasted below.

I also tried the run my app with the option :
"camel.springboot.stream-caching-enabled: false" in my application.yml but
the bug is caused before this option is valuated (in the HTTP Client) so,
it's not effective.

I don't really know if it's me who does not fully understand this option or
if it's a bug. I think, the HTTP Client expects the user to read the
response inside the handler given in the execute method, then, it can
safely close the InputStream. But, in the Camel case, the handler just put
the InputStream reference inside the exchange. Maybe, when the option
disableStreamCache is set to true, the final user of Camel should be able
to give his own handler's implementation in order to consume it in the way
we need?

Thanks in advance for your inputs.
Regards,

Steps to reproduce :

I noticed this bug by using Camel spring boot 4.2.2 but I think this bug is
reproducible without Spring Boot.

Here find my dependencies in my pom.xml :
<properties>
     <camel.version>4.4.2</camel.version>
</properties>

<dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter</artifactId>
     </dependency>

       <dependency>
           <groupId>org.apache.camel.springboot</groupId>
          <artifactId>camel-spring-boot-starter</artifactId>
          <version>${camel.version}</version>
     </dependency>
     <dependency>
           <groupId>org.apache.camel.springboot</groupId>
          <artifactId>camel-http-starter</artifactId>
          <version>${camel.version}</version>
     </dependency>

      <dependency>
           <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
     </dependency>

       <dependency>
           <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-logging</artifactId>
     </dependency>
</dependencies>

Here my Main class :

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
     }
}

and found my route here :

@Component
public class MyRoute extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        getContext().getTypeConverterRegistry();

        //@formatter:off
        from("timer:test?repeatCount=1")
            .setHeader(Exchange.HTTP_METHOD, constant("GET"))
            .setHeader(Exchange.HTTP_URI, constant("
https://camel.apache.org/components/4.4.x/http-component.html";))
            .to("http:myHttpRequest?disableStreamCache=true")
            .log("body : ${body}")
        .end();
        //@formatter:on
    }
}

Find here the log I got after running this project :

org.apache.camel.StreamCacheException: Error during type conversion from
type: org.apache.hc.client5.http.entity.LazyDecompressingInputStream to the
required type: org.apache.camel.StreamCache with value
org.apache.hc.client5.http.entity.LazyDecompressingInputStream@2e74480e due
to org.apache.camel.TypeConversionException: Error during type conversion
from type: org.apache.hc.client5.http.entity.LazyDecompressingInputStream
to the required type: org.apache.camel.StreamCache with value
org.apache.hc.client5.http.entity.LazyDecompressingInputStream@2e74480e due
to java.io.IOException: Attempted read on closed stream.
at
org.apache.camel.impl.engine.StreamCachingHelper.handleException(StreamCachingHelper.java:86)
~[camel-base-engine-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.engine.StreamCachingHelper.handleException(StreamCachingHelper.java:81)
~[camel-base-engine-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.engine.StreamCachingHelper.tryStreamCache(StreamCachingHelper.java:74)
~[camel-base-engine-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.engine.StreamCachingHelper.convertToStreamCache(StreamCachingHelper.java:55)
~[camel-base-engine-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.engine.CamelInternalProcessor$StreamCachingAdvice.before(CamelInternalProcessor.java:1009)
~[camel-base-engine-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.engine.CamelInternalProcessor$StreamCachingAdvice.before(CamelInternalProcessor.java:999)
~[camel-base-engine-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.engine.CamelInternalProcessor.process(CamelInternalProcessor.java:317)
~[camel-base-engine-4.4.2.jar:4.4.2]
at org.apache.camel.processor.Pipeline$PipelineTask.run(Pipeline.java:102)
~[camel-core-processor-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.engine.DefaultReactiveExecutor$Worker.doRun(DefaultReactiveExecutor.java:199)
~[camel-base-engine-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.engine.DefaultReactiveExecutor$Worker.executeReactiveWork(DefaultReactiveExecutor.java:189)
~[camel-base-engine-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.engine.DefaultReactiveExecutor$Worker.tryExecuteReactiveWork(DefaultReactiveExecutor.java:166)
~[camel-base-engine-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.engine.DefaultReactiveExecutor$Worker.schedule(DefaultReactiveExecutor.java:148)
~[camel-base-engine-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.engine.DefaultReactiveExecutor.scheduleMain(DefaultReactiveExecutor.java:59)
~[camel-base-engine-4.4.2.jar:4.4.2]
at org.apache.camel.processor.Pipeline.process(Pipeline.java:163)
~[camel-core-processor-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.engine.CamelInternalProcessor.processNonTransacted(CamelInternalProcessor.java:354)
~[camel-base-engine-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.engine.CamelInternalProcessor.process(CamelInternalProcessor.java:330)
~[camel-base-engine-4.4.2.jar:4.4.2]
at
org.apache.camel.component.timer.TimerConsumer.sendTimerExchange(TimerConsumer.java:293)
~[camel-timer-4.4.2.jar:4.4.2]
at
org.apache.camel.component.timer.TimerConsumer$1.doRun(TimerConsumer.java:164)
~[camel-timer-4.4.2.jar:4.4.2]
at
org.apache.camel.component.timer.TimerConsumer$1.run(TimerConsumer.java:136)
~[camel-timer-4.4.2.jar:4.4.2]
at java.base/java.util.TimerThread.mainLoop(Timer.java:566) ~[na:na]
at java.base/java.util.TimerThread.run(Timer.java:516) ~[na:na]
Caused by: org.apache.camel.TypeConversionException: Error during type
conversion from type:
org.apache.hc.client5.http.entity.LazyDecompressingInputStream to the
required type: org.apache.camel.StreamCache with value
org.apache.hc.client5.http.entity.LazyDecompressingInputStream@2e74480e due
to java.io.IOException: Attempted read on closed stream.
at
org.apache.camel.converter.stream.StreamCacheBulkConverterLoader.convertTo(StreamCacheBulkConverterLoader.java:62)
~[camel-support-4.4.2.jar:4.4.2]
at
org.apache.camel.spi.BulkTypeConverters.convertTo(BulkTypeConverters.java:122)
~[camel-api-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.converter.CoreTypeConverterRegistry.tryCachedConverters(CoreTypeConverterRegistry.java:419)
~[camel-base-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.converter.CoreTypeConverterRegistry.doConvertTo(CoreTypeConverterRegistry.java:378)
~[camel-base-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.converter.CoreTypeConverterRegistry.doConvertToAndStat(CoreTypeConverterRegistry.java:272)
~[camel-base-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.converter.CoreTypeConverterRegistry.convertTo(CoreTypeConverterRegistry.java:167)
~[camel-base-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.engine.DefaultStreamCachingStrategy.doCache(DefaultStreamCachingStrategy.java:275)
~[camel-base-engine-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.engine.DefaultStreamCachingStrategy.cache(DefaultStreamCachingStrategy.java:252)
~[camel-base-engine-4.4.2.jar:4.4.2]
at
org.apache.camel.impl.engine.StreamCachingHelper.tryStreamCache(StreamCachingHelper.java:68)
~[camel-base-engine-4.4.2.jar:4.4.2]
... 18 common frames omitted
Caused by: java.io.IOException: Attempted read on closed stream.
at
org.apache.hc.core5.http.io.EofSensorInputStream.isReadAllowed(EofSensorInputStream.java:107)
~[httpcore5-5.2.4.jar:5.2.4]
at
org.apache.hc.core5.http.io.EofSensorInputStream.read(EofSensorInputStream.java:116)
~[httpcore5-5.2.4.jar:5.2.4]
at
java.base/java.util.zip.CheckedInputStream.read(CheckedInputStream.java:59)
~[na:na]
at
java.base/java.util.zip.GZIPInputStream.readUByte(GZIPInputStream.java:266)
~[na:na]
at
java.base/java.util.zip.GZIPInputStream.readUShort(GZIPInputStream.java:258)
~[na:na]
at
java.base/java.util.zip.GZIPInputStream.readHeader(GZIPInputStream.java:164)
~[na:na]
at java.base/java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:79)
~[na:na]
at java.base/java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:91)
~[na:na]
at
org.apache.hc.client5.http.entity.GZIPInputStreamFactory.create(GZIPInputStreamFactory.java:61)
~[httpclient5-5.2.3.jar:5.2.3]
at
org.apache.hc.client5.http.entity.LazyDecompressingInputStream.initWrapper(LazyDecompressingInputStream.java:51)
~[httpclient5-5.2.3.jar:5.2.3]
at
org.apache.hc.client5.http.entity.LazyDecompressingInputStream.available(LazyDecompressingInputStream.java:86)
~[httpclient5-5.2.3.jar:5.2.3]
at org.apache.camel.util.IOHelper.copy(IOHelper.java:178)
~[camel-util-4.4.2.jar:4.4.2]
at org.apache.camel.util.IOHelper.copy(IOHelper.java:164)
~[camel-util-4.4.2.jar:4.4.2]
at org.apache.camel.util.IOHelper.copy(IOHelper.java:159)
~[camel-util-4.4.2.jar:4.4.2]
at org.apache.camel.util.IOHelper.copyAndCloseInput(IOHelper.java:232)
~[camel-util-4.4.2.jar:4.4.2]
at org.apache.camel.util.IOHelper.copyAndCloseInput(IOHelper.java:228)
~[camel-util-4.4.2.jar:4.4.2]
at
org.apache.camel.converter.stream.StreamCacheConverter.convertToStreamCache(StreamCacheConverter.java:54)
~[camel-support-4.4.2.jar:4.4.2]
at
org.apache.camel.converter.stream.StreamCacheBulkConverterLoader.doConvertTo(StreamCacheBulkConverterLoader.java:80)
~[camel-support-4.4.2.jar:4.4.2]
at
org.apache.camel.converter.stream.StreamCacheBulkConverterLoader.convertTo(StreamCacheBulkConverterLoader.java:53)
~[camel-support-4.4.2.jar:4.4.2]
... 26 common frames omitted

Reply via email to