Camel http4 component 411 Bad Content-Length - apache-camel

The route :
from("direct:start")
.setProperty(Exchange.CHARSET_NAME, constant("iso-8859-1"))
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
Message m = exchange.getOut();
m.setBody(exchange.getIn().getBody());
m.setHeader(Exchange.HTTP_METHOD, HttpMethods.POST);
m.setHeader(Exchange.CONTENT_ENCODING, "gzip" );
m.setHeader(Exchange.CONTENT_LENGTH, m.getBody(byte[].class).length );
m.setHeader(HttpHeaders.CONTENT_TYPE, "application/xml");
m.setHeader(Exchange.HTTP_CHARACTER_ENCODING, "iso-8859-1");
m.setHeader(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate");
}
})
.marshal().gzip()
.to("http4://remote.com/path")
.unmarshal().gzip();
What I am sending :
String body = "<?xmlversion=\"1.0\"encoding=\"ISO-8859-1\"?><theXml></theXml>";
producer.sendBody(body);
I am getting
HTTP operation failed invoking http://remote.com/path with statusCode: 411
What is missing/wrong with this route ?
EDIT
The correct route would be
from("direct:start")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
Message m = exchange.getOut();
m.setBody(exchange.getIn().getBody());
m.setHeader(Exchange.HTTP_METHOD, HttpMethods.POST);
m.setHeader(Exchange.CONTENT_ENCODING, "gzip" );
m.setHeader(Exchange.CONTENT_TYPE, "application/xml");
}
})
// http4 takes care of compressing/decompressing gzip
.to("http4://remote.com/path")
But now I have another problem : the remote server does not handle "Transfer-Encoding: Chuncked" Which seems to be the default way camel-http4 does it.
And i can't figure out how to turn Chunked off.
See next question How to turn off “Transfer-Encoding Chuncked” in Camel-http4?

You are setting the content length from the length of the unencoded data. It should probably be the length of the transmitted data. Refer to this SO question:
content-length when using http compression
By the way, do you really need to gzip with the data format?
There is a Unit test in camel sending GZIPed data.
https://svn.apache.org/repos/asf/camel/trunk/components/camel-http4/src/test/java/org/apache/camel/component/http4/HttpCompressionTest.java

Related

using camel http handling CachedOutputStream

hi~ i am using camel http component. and i can't extract body message.
here is my code
.log(LoggingLevel.INFO, "ToUri ===> ${body}")
.toD("${body}")
.log(LoggingLevel.INFO, "Result ===> ${body}")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
long startTime = System.currentTimeMillis();
Message inboundMessage = exchange.getIn();
Object body = exchange.getIn().getBody();
String msg = inboundMessage.getBody(String.class);
System.out.println("body:"+body);
System.out.println("getInBody msg:"+msg);
System.out.println("getInBody body:"+body.toString());
=======================================================================
body : org.apache.camel.converter.stream.CachedOutputStream$WrappedInputStream#28936ba4
getInBody msg:
getInBody bodybodybody:org.apache.camel.converter.stream.CachedOutputStream$WrappedInputStream#28936ba4
the log is good works. like this
09:56:53.523 INFO route1 - ToUri ===> https://translation.googleapis.com/language/translate/v2?key=tesetKey&source=en&target=ja&q=hi
09:56:54.545 INFO route1 - Result ===> {
"data": {
"translations": [
{
"translatedText": "こんにちは"
}
]
}
}
i want to extract translatedText using camel.
how can i handle CachedOutputStream and what is this?
i search camel doc.and cant understand.please give me a hint to solve my problem.
thanks.
See stream-caching for information about CachedOutputStream: http://camel.apache.org/stream-caching.html
To get the message body as string from the processor, you just do
String body = exchange.getIn().getBody(String.class);
That will tell Camel that you want the message as a String and it will automatic covert the message body from CachedOutputStream to String. Then you can grab that text you want via regular Java code.
Also note there is jsonpath you can use to work with json data and get information, however its syntax can take a little bit to learn: http://camel.apache.org/jsonpath
You already took the stream data (with .log) before calling processor. Stream data can only fetched once apparently. Try remove the log step and you can get in the processor:
.log(LoggingLevel.INFO, "Result ===> ${body}")
.process(new Processor() {
after spend 2 days experimenting
you can use convertBodyTo(Class<?> type) method for that, like this
.log(LoggingLevel.INFO, "Result ===> ${body}")
.convertBodyTo(String.class)
.process(new Processor() { ... }

Keep part of URI encoded in camel route

I am new to camel, so this may be a simple problem to solve.
I have a spring-boot application with camel components which interacts with GitLab API.
My problem is that I need to keep the endpoint URIs in camel routes encoded, for example:
from("direct:start")
.setHeader("PRIVATE-TOKEN",constant("myToken"))
.to("https://gitlab.com/api/v4/projects/12345/repository/files/folder%2Ffile%2Eextension/raw?ref=master")
When the route starts, the message is sent to
"https://gitlab.com/api/v4/projects/12345/repository/files/folder/file.extension/raw?ref=master"
which returns 404, because the parameter file_path has to be encoded, as said in the GitLab doc (I've cheked with a GET from curl: with the first URI a json is returned, with the second 404).
I tried to pass the last part of the URI as HTTP_QUERY, but in this case there is the "?" between it and the URI and I get 404 again:
https://gitlab.com/api/v4/projects/12345/repository/files/?folder%2Ffile%2Eextension/raw?ref=master
I tried adding the URI with the headerHTTP_URI: this time the URI is reached correctly, but I get null body instead of the json answer.
Any idea to solve this issue?
I see that you already tried using HTTP_URI header. How did you set it? Try this:
from("direct:start")
.setHeader("PRIVATE-TOKEN", constant("myToken"))
.setHeader(Exchange.HTTP_URI, simple("https://gitlab.com/api/v4/projects/12345/repository/files/folder%2Ffile%2Eextension/raw?ref=master"))
.to("http:dummy");
This way you set the URI during the route execution, not in endpoint definition. According to docs:
Exchange.HTTP_URI: URI to call. Will override existing URI set directly on the endpoint. This URI is the URI of the HTTP server to call. Its not the same as the Camel endpoint URI, where you can configure endpoint options such as security etc. This header does not support that, its only the URI of the HTTP server.
Don't forget the dependency:
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-http</artifactId>
</dependency>
The test:
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:start")
.setHeader("PRIVATE-TOKEN", constant("myToken"))
.setHeader(Exchange.HTTP_URI, simple("http://0.0.0.0:8080?param=folder%2Ffile%2Eextension/raw&ref=master"))
.to("http:dummy");
from("jetty:http://0.0.0.0:8080?matchOnUriPrefix=true")
.setBody(constant("{ key: value }"))
.setHeader(Exchange.CONTENT_TYPE, constant(MediaType.APPLICATION_JSON_VALUE))
.to("mock:result");
}
};
}
#Test
public void test() throws InterruptedException {
getMockEndpoint("mock:result").expectedHeaderReceived(Exchange.HTTP_QUERY, "param=folder%2Ffile%2Eextension/raw&ref=master");
final Exchange response = template.send("direct:start", new Processor() {
public void process(Exchange exchange) throws Exception {
// nothing
}
});
assertThat(response, notNullValue());
assertThat(response.getIn().getHeader(Exchange.HTTP_URI).toString(), containsString("folder%2Ffile%2"));
assertThat(response.getOut().getBody(String.class), containsString("{ key: value }"));
assertMockEndpointsSatisfied();
}
I tried adding the URI with the headerHTTP_URI: this time the URI is reached correctly, but I get null body instead of the json answer.
Keep in mind that the response should be stored at the OUT body:
Camel will store the HTTP response from the external server on the OUT body. All headers from the IN message will be copied to the OUT message, so headers are preserved during routing. Additionally Camel will add the HTTP response headers as well to the OUT message headers.

camel splitter - stop looping on specific exception

How can we stop looping on camel splitter on specific exception?
The "stopOnException()" is stopping the looping for every exception, but instead I want to stop looping only on some specific exceptions. And if the exception is "HttpOperationFailedException", I want to stop looping based on response code.
For example if response code is "500" stop execution and if response code is 404 continue execution.
Is it possible?
Original Question
from("timer:categoryRouter?delay=0")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
exchange.getIn().setBody("A,F,B,D,C");
}
})
// tell Splitter to use the aggregation strategy which handles and ignores exceptions
.split(body(), new MyIgnoreFailureAggregationStrategy())
.stopOnException()
// log each splitted message
.log("Split line ${body}")
// and have them translated into a quote
.bean(WordTranslateBean.class)
// and send it to a mock
.to("mock:split")
.end()
// log the outgoing aggregated message
.log("Aggregated ${body}")
// and send it to a mock as well
.to("mock:result");
Bean which throws exception:
public class WordTranslateBean {
private Map<String, String> words = new HashMap<String, String>();
public WordTranslateBean() {
words.put("A", "Camel rocks");
words.put("B", "Hi mom");
words.put("C", "Yes it works");
}
public String translate(String key) throws HttpOperationFailedException {
if (!words.containsKey(key)) {
HttpOperationFailedException httpOperationFailedException = null;
if(key.equals("F")) {
httpOperationFailedException = new HttpOperationFailedException("uri",500,"Internal Server Error","location",null,"Key not a known word " + key);
}
else {
httpOperationFailedException = new HttpOperationFailedException("uri",404,"Resource Not Found","location",null,"Operation not supported on word " + key);
}
throw httpOperationFailedException;
}
return words.get(key);
}
}
Working Solution:
from("timer:categoryRouter?delay=0")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
exchange.getIn().setBody("A,F,B,D,C");
}
})
// tell Splitter to use the aggregation strategy which handles and ignores exceptions
.split(body(), new MyIgnoreFailureAggregationStrategy())
.stopOnException()
// log each splitted message
.log("Split line ${body}")
// and have them translated into a quote
.doTry()
.bean(WordTranslateBean.class)
// and send it to a mock
.to("mock:split")
.doCatch(HttpOperationFailedException.class)
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
HttpOperationFailedException e = (HttpOperationFailedException) exchange.getProperty(Exchange.EXCEPTION_CAUGHT);
if(e.getStatusCode()!=404){
throw e;
}
}
})
.end()
.end()
// log the outgoing aggregated message
.log("Aggregated ${body}")
// and send it to a mock as well
.to("mock:result");
Why don't you throw a custom exception based on a response code ? That's one option . Basically you can catch the original http exception , check the response code , throw your custom exception. Can you post your route ? It's easy to implement this way, just want to see how you have organised your routes .
Basically we still need to use "stopOnException" to stop the splitter when exception occurred. But to control on which exception the splitter should break, you can use "doTry..doCatch" block and in the respective catch block throw back the exception again.
from("timer:categoryRouter?delay=0")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
exchange.getIn().setBody("A,F,B,D,C");
}
})
// tell Splitter to use the aggregation strategy which handles and ignores exceptions
.split(body(), new MyIgnoreFailureAggregationStrategy())
// log each splitted message
.log("Split line ${body}")
// and have them translated into a quote
.doTry()
.bean(WordTranslateBean.class)
// and send it to a mock
.to("mock:split")
.doCatch(HttpOperationFailedException.class)
.log("Ignore Exception")
.doCatch(IOException.class)
.throwException(new IOException())
.doCatch(UnsupportedOperationException.class)
.log("Ignore Exception")
.end()
.end()
// log the outgoing aggregated message
.log("Aggregated ${body}")
// and send it to a mock as well
.to("mock:result");
If the exception is related to http and want to inspect the response code to act accordingly then you can my question which has the working solution.
You can catch the exceptions and decide what to do with them. Inside of your splitter:
<doTry>
<!-- Your Splitter logic here -->
<doCatch>
<exception>java.lang.IllegalStateException</exception>
<log message="This exception happened here, but not a problem.."/>
</doCatch>
<doCatch>
<exception>java.io.IOException</exception>
<log message="Big problem here. STOPPING.."/>
<stop/>
</doCatch>
<doFinally>
<to uri="mock:finally"/>
</doFinally>
</doTry>

Apache Camel - Dead Letter Channel - enrich message

I'm using deadLetterChannel to take care of exceptions and send them to the error queue.
errorHandler(deadLetterChannel(QUEUE_ERROR).maximumRedeliveries(3).redeliveryDelay(2000));
Is it possible to enrich the message with additional message headers? Or do i have to use onException for it?
You can use onRedelivery and with a processor to add headers before redelivering
errorHandler(deadLetterChannel(QUEUE_ERROR).maximumRedeliveries(3).redeliveryDelay(2000).onRedelivery(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
//add headers here
}
}));

Camel onException doesn't catch NoMessageIdException of idempotentConsumer?

Example route:
onException(Exception.class)
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
System.out.println("it works");
}
})
.handled(true);
from("jetty://http://0.0.0.0:8888/test")
.idempotentConsumer(header("myid"), MemoryIdempotentRepository.memoryIdempotentRepository(1000000))
.skipDuplicate(false)
.filter(property(Exchange.DUPLICATE_MESSAGE).isEqualTo(true))
.throwException(new DuplicateRequestException())
.end();
Sending a request to the listener URL without myid parameter throws org.apache.camel.processor.idempotent.NoMessageIdException: No message ID could be found using expression: header(myid) on message exchange: Exchange[Message: [Body is instance of org.apache.camel.StreamCache]]
without ever passing from onException.
Yes this is in fact a bug in Apache Camel. I have logged a ticket to get this fixed in the next releases.
https://issues.apache.org/jira/browse/CAMEL-7990

Resources