We use the Apache Camel's Netty4 HTTP Component for almost everything on the artifact I have a problem with right now.
The issue is that, a proxy is now required to make external/outbound calls to the Internet...and eventually this is not working. All the calls are getting blocked/rejected — and just for the record, we had the same issue in a different artifact and we were able to circumvent it by using the JVM proxy settings, but this one is using the Async Http Client.
I've tried setting the proxy at the JVM level by using http.proxyHost, http.proxyPort, etc., but it didn't work this time.
Question(s): Is there a way to configure any proxy settings within this component? I've been digging inside org.apache.camel.component.netty4.http.NettyHttpConfiguration to see if there are any relevant settings I can change/use — I'm not completely sure what HTTP client it's used behind scenes and I'm guessing it might be Netty O:)
If relevant, (one of) our routes looks like:
#Component
public final class Route extends AbstractRouteBuilder {
#Override
public void configure() throws Exception {
super.configure();
from("{{route.inbound.reports}}") // netty4-http:https://hostname.tld/api/v1/reports
.choice()
.when(header(Exchange.HTTP_METHOD).isEqualToIgnoreCase(HttpMethod.GET))
.toD("seda:get")
.choice()
.when(AbstractHelper::isOk)
.setProperty("source", constant("user"))
.to("seda:retrieve?timeout={{async.timeout:4500}}")
.setBody(simple("${property.results}"))
.marshal().json(JsonLibrary.Jackson)
.end()
.endChoice()
.otherwise()
.toD("{{route.artifact.reports}}");
from("seda:get")
.toD("{{route.artifact.reports}}")
.unmarshal().json(JsonLibrary.Jackson)
.to(exec("analyze"));
from("seda:retrieve")
.filter(PredicateBuilder.and(header(Key.ACCOUNT_ID).isNotNull()))
.setHeader(Exchange.HTTP_METHOD, constant(HttpMethod.GET))
.setHeader(Header.API_KEY, simple("{{vendor.api-key}}"))
.toD("{{route.outbound.reports}}") // netty4-http:https://external-hostname.tld/api/client/reports
.unmarshal().json(JsonLibrary.Jackson)
.choice()
.when(AbstractHelper::isOk)
.to(exec("aggregate"))
.otherwise()
.to(exec("handleFailure"))
.end();
}
// ...
}
I guess there is no way around this one...or at least, not something I could find within the allowed timeframe.
We ended-up using a different component for all external/outbound calls (that obey the proxy rules); specifically:
AHC / camel-ahc
HTTP4 / camel-http4
Related
I am using Apache Camel to implement Rest APIs. I've 2 RouteBuilder types defining all the Camel routes, my application needs. All REST endpoints reside in RestRouter, and it frames the execution using CustomRouter. For example, I've RestRouter to hold my REST routes
public class RestRouter extends RouteBuilder
{
#Override
public void configure() throws Exception
{
rest("/sample")
.post()
.route()
.routeId("postSample")
.to("direct:validate")
.to("direct:save")
.endRest();
}
}
And another RouteBuilder called CustomRouter to bundle non-REST routes.
public class CustomRouter extends RouteBuilder
{
#Override
public void configure() throws Exception
{
onException(ValidationException.class)
.handled(true)
.setBody(simple("${exchangeProperty[CamelExceptionCaught]}"))
.to("bean:exceptionHandler?method=constraintViolationHandler")
.setHeader(Exchange.CONTENT_TYPE, constant(ErrorResponse.class.getName()))
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(HttpStatus.SC_BAD_REQUEST));
validator()
.type(Sample.class)
.withBean("sampleValidator");
from("direct:validate")
.to("bean-validator://x"); // Runs javax/Hibernate annotated validations
from("direct:save")
.routeId("saveSample")
.inputTypeWithValidate(Sample.class)
.to("bean:sampleRepository?method=save");
}
}
Validation bean SampleValidator is a Camel org.apache.camel.spi.Validator which throws org.apache.camel.ValidationException for any violations.
Problem with setup is that Camel doesn't invoke my custom exception handler for ValidationException. Validation exception occurs for route saveSample. Here's my finding on how it goes further inside Camel processor types.
Control goes to RedeliveryErrorHandler's handleException() where it looks for the exception policy. Root of failing exchange (i.e. RestRouter -> postSample) is expected here to define the exception handler.
Later, Camel goes to failing UnitOfWork (i.e. to saveSample) to identify the exception handler.
That means, for below expression, routeId is from CustomRouter and exceptionPolicy is from the RestRouter. Combination never exists and Camel fails to find the exception processor.
processor = exceptionPolicy.getErrorHandler(routeId)
In above context, I've following questions
Is it a good practice to divide a functionality within multiple RouterBuilder types?
Shouldn't Camel use current UnitOfWork to resolve the exception policy?
Is there some way Camel can invoke my custom handler, provided different RouteBuilder types?
Edit
I can't move to have a single RouterBuilder.
One, because I've an Apache Avro object coming in payload for post from another orchestration service, and then transformation to my JPA entities is done via the bean methods, not using Camel's Transformer. This arrangement doesn't fit with how Camel invokes the Validator (seeContractAdvice). ContractAdvice is a CamelInternalProcessorAdvice which applies Transformer (if intype != outtype) and Validator.
Second, moving to single RouterBuilder will need me to move Avro-to-Entity logic to a Camel Transformer, and that approach would differ greatly with the way we're doing currently. But yes, single RouterBuilder + Transformer + Validator should work.
Have a look at this example from Camel In Action which demonstrates how to reuse the error-handling across route builders defined in Java DSL.
BaseRouteBuilder
and InboxRouteBuilder and OrderRouteBuilder
You can create a base class where you setup the context-scoped error configuration.Then your RouteBuilder classes are extending this base class and calling calling super.configure to get the common configuration.
See if it works when you have all the routes in a single RouteBuilder. "Global" exception handlers such as yours are not really global as they are applied to all routes built by that specific builder, so I wouldn't expect your onException to be applied to the REST route.
Alternatively move the onException in to the REST builder. The handler sets HTTP status codes, so on the surface looks like it would be better packaged with REST routes.
I have a Camel route definition like this one:
#Component
public class AggregateRouter extends AbstractRouteBuilder {
#Override
public void configure() throws Exception {
super.configure();
from("{{endpoint.users}}/{id}?matchOnUriPrefix=true")
.to("bean:routeUtils?method=validateQueryParams"))
.to("bean:routeUtils?method=loadRouteProperties"))
.to("{{uri.api.users}}")
.unmarshal().json(JsonLibrary.Jackson, Map.class)
.to("bean:routeUtils?method=extractAndAddToProperty"))
.to("bean:routeUtils?method=prepareAggregateRestCalls"))
.multicast()
.stopOnException()
.to("seda:operation1")
.to("seda:operation2")
.end()
.setBody(simple("${property.result}"))
.marshal().json(JsonLibrary.Jackson)
.setHeader(Exchange.HTTP_METHOD, constant("GET"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"));
from("seda:operation2")
.toD("{{uri.api.users.operation2}}")
.unmarshal()
.json(JsonLibrary.Jackson, List.class)
.to("bean:userService?method=addOp2"));
from("seda:operation1")
.toD("{{uri.api.users.operation1}}")
.choice()
.when(AbstractHelper::isOk)
.unmarshal()
.json(JsonLibrary.Jackson, List.class)
.to("bean:userService?method=addOp1"))
.otherwise()
.unmarshal()
.json(JsonLibrary.Jackson, Map.class)
.to("bean:userService?method=handleRouteSubscriptionException"))
.end();
}
}
I want to be able to use this definition only when the HTTP request comes into the integration layer as a GET request. The issue now is: I have two more operations (PUT and DELETE), but I don't want a "special" processing for those two (at least for now)...and they are behaving as GET since this route definition is "intercepting" and handling the request(s).
I can't use a Rest DSL (the project is currently like). I also tried using the &httpMethodRestrict like {{endpoint.users}}/{id}?matchOnUriPrefix=true&httpMethodRestrict=PUT but it's not working also.
Any clues?
i also think that httpMethodRestrict is the way to go. The docs are quite vague about the parameter... Try to use it like httpMethodRestrict=GET (read: restrict the requests to GET)
Another possible solution might be using the header information Exchange.HTTP_METHOD like .filter(header("Exchange.HTTP_METHOD").isEqualTo("GET")) - just to get the idea (i didn't try it out)
The problem
I have a spring mvc application that uses apache camel. I am confused on the role that the RouteBuilder class plays and how it actually gets initialized. I know that the docs say that the configure() method is:
Called on initialization to build the routes using the fluent builder syntax.
but when does this initialization occur? Does it occur at application startup or some time later when the route is about to be used?
The purpose of this question is ultimately to ask how I can modify the route at runtime. I want to be able to build different routes as needed.
Examples
xml definitions:
<service name="myService" tier="3">
<requestType>my.package.RequestType</requestType>
<responseType>my.package.ResponseType</responseType>
<endpoint>
<httpEndpoint>
<url default="true" value="someUrl"/>
<timeout value="5000"/>
</httpEndpoint>
</endpoint>
</service>
Route Builder template:
public class myRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
// When does this method get executed?
}
}
Questions
When does configure() execute?
How can I dynamically set the endpoint url?
You are able to use toD to dynamically change the endpoint at runtime based on an expression. See the documentation
If you want to change more of the route or add a completely new route then look at the API on the CamelContext. This Stackoverflow question has an example of adding a completely new route.
The lifecycle of the Camel service is documented here : https://camel.apache.org/lifecycle.html
Camel uses a simple lifecycle interface called Service which has a single start() and stop() method.
Various classes implement Service such as CamelContext along with a number of Component and Endpoint classes.
When you use Camel you typically have to start the CamelContext which will start all the various components and endpoints and activate the routing rules until the context is stopped again.
It is when the context starts that the various components start. Not sure i understand the dynamic url part. If it is to indicate a dynamic endpoint (if the data is this , then queue1 else queue2) you should be able to use something like the DynamicRouter EIP which is as explained here (https://camel.apache.org/dynamic-router.html)
You have several options.
Inject them as spring properties.
Inject them from external properties source.
Inject them from some bean method.
Then you can put the property value in a header and later put the value in the .toD("$header.routeEndpoint"). This can take care of dynamic endpoints.
Off course to rebuild the entire route you need to play with the API.
I'm using the exchange headers to store any variables in the route. But, looks like these headers will be carried on to the other routes which are called from this route.
In the below sample, I'm calling a getContact route which will call a http endpoint. But, it will also send the headers, variable1 & variable2, it got from the initial route, direct:start.
from("direct:start")
.setHeader("variable1", constant("value1"))
.setHeader("variable2", constant("value2"))
.to("direct:getContact");
from("direct:getContact")
.setHeader("Content-Type", constant("application/json"))
.setHeader("Accept", constant("application/json"))
.setHeader(Exchange.HTTP_METHOD, constant("GET"))
.to("http://<host>:<port>/contact/3")
.unmarshal().json(JsonLibrary.Jackson);
Is there a way to avoid this? In contrast, a method call in java will hide all the existing variables by context switch.
I've run into the problem before when sending a webservice call using http4. Tt's rather annoying that Camel seems to send send the entire exchange when you're using the http4/http endpoint. I got around this by using a content enricher. I placed the actual call using http4 in the enrich route and had an simple aggregation strategy combine the two messages afterwards.
Alternatively, you can make the call in a bean. This way you lose some of the benefits of camel but you have complete control over the call body.
There is no direct way to avoid this. If you are setting the headers to a hard-coded value then you might be able to move the header to a URI property on your endpoint. If not then you only really have 2 other options. The first option is to remove all of the headers using a remove header call after your HTTP call so they don't go downstream. The second is to set all of the headers in the same route as the http call and have a different route call that endpoint with an enrich statement and in the aggregation back to the main route you can customize the returned exchange.
Here is an camel http reference page for all of the allowed headers to see if you can put it in the URI http://camel.apache.org/http4.html
Sample of a route removing headers
from("direct:start")
.setHeader("variable1", constant("value1"))
.setHeader("variable2", constant("value2"))
.setHeader("Content-Type", constant("application/json"))
.setHeader("Accept", constant("application/json"))
.setHeader(Exchange.HTTP_METHOD, constant("GET"))
.to("http://<host>:<port>/contact/3")
.unmarshal().json(JsonLibrary.Jackson)
.removeHeaders("variable*")
.to("Anything I call now won't have the variable headers");
enrichment call
AggregationStrategy aggregationStrategy = new ExampleAggregationStrategy();
from("direct:start")
.enrich("direct:getContact", aggregationStrategy)
.to("You can have no additional headers here");
public class ExampleAggregationStrategy implements AggregationStrategy {
public Exchange aggregate(Exchange original, Exchange resource) {
Object originalBody = original.getIn().getBody();
Object resourceResponse = resource.getIn().getBody();
Object mergeResult = //TODO implement this however you want. You can remove any headers here you like
if (original.getPattern().isOutCapable()) {
original.getOut().setBody(mergeResult);
} else {
original.getIn().setBody(mergeResult);
}
return original;
}
}
Actually 1 more option came to mind when going through the camel documentation I found an interesting property. Disclaimer I have never tried this property myself since I am still running camel 2.15 atm, but feel free to test it really quick it might just be what you need.
copyHeaders
default: true
Camel 2.16: If this option is true then IN exchange headers will be copied to OUT exchange headers according to copy strategy. Setting this to false, allows to only include the headers from the HTTP response (not propagating IN headers).
Just use:
.removeHeaders("variable*")
to remove headers of any pattern.
I am currently using a HTTP method for invoking some URL which will create a JIRA issue.
Now I want to use Apache Camel, how can I use that?
I need to invoke the following link through Camel:
http://localhost:8080/rest/api/2/project/" + key + /components
As I'm new to Camel, please suggest some solutions and examples too.
Thanks
See also this FAQ about using dynamic to endpoints in Camel
http://camel.apache.org/how-do-i-use-dynamic-uri-in-to.html
Essentially the EIP pattern for this is the recipient list.
So in your case it could also be simplified to as one EIP
<recipientList>
<simple>http://localhost:8080/rest/api/2/project/${header.myKey}/components</simple>
</recipientList>
Mind the http component in Camel is fully synchronous. If you want to do request/reply over HTTP and avoid having the caller block while waiting for the reply message, then you can use some of the other HTTP components from Camel such as:
camel-ahc
camel-http4
camel-jetty
You could easily use the CXFRS Component; if you need to do it using the HTTP Component for some reason you could easily use that as well:
<setHeader headerName="CamelHttpUri">
<simple>http://localhost:8080/rest/api/2/project/${header.myKey}/components</simple>
</setHeader>
<inOut uri="http://doesnt.matter.we/override/it/anyways" />
And of course you will need to enrich your message with the myKey header before getting to this part of the route.
I am using apache camel jetty
CamelContext context = new DefaultCamelContext();
public void configure(){
context.addRoutes(new RouteBuilder(){
from("jetty:localhost:9000/offers")
.to("direct:getOffers")
.end();
}
});
so here when the user will hit http://localhost:9000/offers then the endpoint direct:getOffers will get invoked
so now defining the getOffers end point
context.addRoutes(new RouteBuilder(){
public void configure(){
from("direct:getOffers")
.to("jetty:http://localhost:9008/api/v2.0/offers?
bridgeEndpoint=true")
.end();
}
});
Here another service is running at 9008 having a rest resource of
http://localhost:9008/api/v2.0/offers and this is the resource that i am trying to consume.
so when camel instance starts it registers both the routes then it does the processing as described above
Note Its important to add the option of ?bridgeEndpoint=true for this to work
You can consume REST service from camel using CXFRS Component.Apache camel has enough information about this.
http://camel.apache.org/cxfrs.html