is that possible to do this in camel:
2 rest services standing on jetty, first by http (for example on port 1234) and second https (for example on port 4321), how can I configure it? Is that possible?
Effect which i need to receive (example urls):
http://localhost:1234/firstHttpMethod
http://localhost:1234/secondHttpMethod
https://localhost:4321/firstHttpsMethod
https://localhost:4321/secondHttpsMethod
For this moment when I'm trying to add 2 routes, only second is working. How to solve that problem (I have a think to do 2 rest services - first on jetty, second on something else, but its not good conception).
code looks like this:
camelContext.addRoutes(firstJettyBuilder());
camelContext.addRoutes(secondJettyBuilder());
protected RouteBuilder firstJettyBuilder()
{
return new RouteBuilder()
{
#Override
public void configure()
throws Exception
{
restConfiguration()
.component("jetty")
.host("localhost")
.port(42300)
.scheme("https")
.bindingMode(RestBindingMode.json)
.dataFormatProperty("json.in.disableFeatures", "FAIL_ON_UNKNOWN_PROPERTIES")
.dataFormatProperty("json.in.enableFeatures", "FAIL_ON_NULL_FOR_PRIMITIVES");
configureSSL();
}
private void configureSSL()
{
final JettyHttpComponent jettyComponent = camelContext.getComponent("jetty", JettyHttpComponent.class);
final Map<String, Object> sslSocketConnectorProperties = new HashMap<>();
sslSocketConnectorProperties.put("keyStorePath", KEYSTORE);
sslSocketConnectorProperties.put("trustStorePath", KEYSTORE);
sslSocketConnectorProperties.put("keyStorePassword", KEYSTORE_PASSWORD);
sslSocketConnectorProperties.put("trustStorePassword", KEYSTORE_PASSWORD);
jettyComponent.setSslSocketConnectorProperties(sslSocketConnectorProperties);
}
};
}
protected RouteBuilder createPosJettyBuilder()
{
return new RouteBuilder()
{
#Override
public void configure()
throws Exception
{
restConfiguration()
.component("jetty")
.host("localhost")
.port(42302)
.scheme("http")
.bindingMode(RestBindingMode.json)
.dataFormatProperty("json.in.disableFeatures", "FAIL_ON_UNKNOWN_PROPERTIES")
.dataFormatProperty("json.in.enableFeatures", "FAIL_ON_NULL_FOR_PRIMITIVES");
}
};
}
Short answer: I don't thnik this is possible in the same Camel context, because of reasons that I can call bugs. It might be possible with different contexts.
Here are some observations after debugging this.
1st try: as in the question.
Camel uses the same Jetty endpoint for both configurations. The second RouteBuilder overwrites the endpoint configuration of the first one. Hence, the expected first server is not running at all.
2nd try: multiple Jetty endpoints.
One may try something like (after creating Jetty endpoint(s) and adding them to the context):
this.restConfiguration("jetty")....
this.rest("/path").... // good
...
this.restConfiguration("jetty-tls")....
this.rest("/path").... // produces exception!
It looks like the rest definitions are added to the Camel context. On creating routes for the second RouteBuilder, the definition from the first one is already there. Camel wants to create 2 routes with the same path and throws exception:
Failed to start route ... because of Multiple consumers for the same endpoint is not allowed: jetty:...
Unfortunately, it is not an option to skip the rest definition in one of the builders.
Bonus try: multiple Jetty endpoints and different paths.
One expects at least this to work:
this.restConfiguration("jetty")....
this.rest("/path1").... // good
...
this.restConfiguration("jetty-tls")....
this.rest("/path2").... // good
No exceptions here, but Camel starts 3 routes!
Related
I have a Spring Boot rest service based application configured on multiple ports that needs to distinguish each request between the port it came through. The idea of having several ports for the application is due to different public and private sub-networks (with different security access levels) that could access different parts of the services exposed by the application.
Conceptually the idea was to add additional connectors to the embedded tomcat and then catch all incoming requests altering them by adding a custom header to each one specifying the "channel" it came through.
The problem I'm facing is that I have no idea how I could catch these incoming requests on a connector level (before it gets to any filter or servlet).
So, for the multi-port solution I have:
#Configuration
public class EmbeddedTomcatConfiguration {
#Value("${server.additional-ports}")
private String additionalPorts;
#Bean
public TomcatServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
Connector[] additionalConnectors = additionalConnector();
if(additionalConnectors != null && additionalConnectors.length > 0) {
tomcat.addAdditionalTomcatConnectors(additionalConnectors);
}
return tomcat;
}
private Connector[] additionalConnector() {
if(StringUtils.isNotBlank(additionalPorts)) {
return Arrays.stream(additionalPorts.split(","))
.map(String::trim)
.map(p -> {
Connector connector = new Connector(Http11NioProtocol.class.getCanonicalName());
connector.setScheme("http");
connector.setPort(Integer.valueOf(p));
return connector;
})
.toArray(Connector[]::new);
}
return null;
}
}
In theory, I could register a custom LifecycleListener to each connector, but, as far as I know, it won't help. I've also heard something about valves, though I'm not sure how to implement them per connector.
Or maybe I'm going a completely wrong way.
I'd really appreciate any help in the matter.
It seems as though you have your heart set on trying a Valve, but, after some more research, I would recommend using a ServletFilter to do this work instead of a Valve.
I believe you can do this work in a Valve, but a Valve must be deployed into the tomcat/lib directory instead of being packaged inside of your application. I would urge you to consider trying to keep your application together in deployable artifact instead of having to remember to deploy one extra jar file to your tomcat instance when creating a new deployment.
From this answer, Difference between getLocalPort() and getServerPort() in servlets you should be able to access the tomcat port by calling getLocalPort() on the HttpServletRequest.
Then based on your idea in the question add a specific context name into the request header.
public class PortContextFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
int localPort = request.getLocalPort();
// if else or case statements
ServletRequest newRequest = PortContextHttpServletRequestWrapper(request, YourPortContext)
filterChain.doFilter(newRequest, response);
}
public void destroy() {
// Nothing to do
}
public void init(FilterConfig filterConfig) throws ServletException {
// Nothing to do.
}
}
After that is done, in theory, you should be able to use the #RequestMapping annotation to route based on the name inside the header.
#PreAuthorize("hasPermission(#your_object, 'YOUR_OBJECT_WRITE')")
#RequestMapping("/yourobject/{identifier}", headers="context=<context_name>", method = RequestMethod.POST)
public String postYourObject(#PathVariable(value = "identifier") YourObject yourObject) {
// Do something here...
...
}
One can also use RequestCondition to route requests based on port, which I think is closer to stock spring. see Spring Boot | How to dynamically add new tomcat connector?
I have a Camel route that is set to run every five minutes
#Component
public class CamelRoute extends RouteBuilder{
private final String comment = "Cron"
#Override
public void setup() {
from("quartz2://myGroup/myTimerName?cron=0+0/5+12-18+?+*+MON-FRI")
.log("Processing from"+comment)
.to("activemq:Totally.Rocks");
}
}
And I want to force it to run manually, from Spring http request, and change comment field in CamelRoute
#RequestMapping(value = "/ex/foos", method = RequestMethod.GET)
#ResponseBody
public String getFoosBySimplePath() {
//TODO: Start Camel route
//change camel log "comment" from "Cron" to "HTTP request"
}
To run a Camel route manually you can use FluentProducerTemplate. You can autowire an instance like a normal bean.
Examples: 1, 2
To be honest, I am not sure if it will work with quartz endpoints, but I am sure it is working pretty well with "direct:" endpoints. Anyway, it could be a good start for your findings.
Solution for my task was easy, although not straightforward if using Camel documentation:
startRoute(String routeId) Starts the given route if it has been
previously stopped
I added another route
from("timer://manualRestart?repeatCount=1")
.routeId("manualRestart")
.noAutoStartup()
.to("activemq:Totally.Rocks");
and use startRoute() to launch it when needed
public String getFoosBySimplePath() {
camelContext.startRoute("manualRestart");
}
Why do I find it "not straightforward"? Because documentation says, that startRoute() can start previously stopped routes. Route was never stopped, it was configured not to start by default.
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'm using Camel to integrate 2 systems. I have defined different routes and one of the routes consumes from a specific rabbitmq queue and send it to a REST service. Nothing fancy here, the route looks like this:
public class WebSurfingRabbitToRestRoute extends RouteBuilder{
#Override
public void configure() throws Exception {
from("rabbitmq://rabbit_host:port/Rabbit_Exchange").
setHeader("CamelHttpMethod", constant("POST")).
setHeader("Content-Type", constant("application/json")).
bean(TransformResponse.class, "transform").
to("http4://rest_service_host:port/MyRestService).
}
}
As you can see, i process every message before sending it to the rest service since i need to adjust some things. The problem comes when i find out that sometimes (i dont know how or when), the system that publish into rabbit, send 2 messages concatenated at once.
What i expect to get is a simple json like this:
[{field1:value1, field2:value2}]
What i sometimes get is:
[{field1:value1, field2:value2},{field1:value3, field2:value4}]
So when i face this scenario, the rest service im routing the message to, fails (obviously).
In order to solve this, i would like to know if there is a way to invoke a route from inside a processor. From the previous snippet of code you can see that Im calling the transform method, so the idea will be to do something like the following pseudo-code, because after the route is already fired, i cant split the events and send them both within the same route "instance", so i thought about invoking a different route that i can call from here which will send the message2 to the very same rest service.
public class TransformRabbitmqResponse {
public String transform(String body) throws Exception {
// In here i do stuff with the message
// Check if i got 2 messages concatenated
// if body.contains("},{") {
// split_messages
// InvokeDifferentRoute(message2)
//}
}
}
Do you guys think this is possible?
One option (though I am not sure this is the best option) would be to split this up into two different routes using a direct endpoint.
public class WebSurfingRabbitToRestRoute extends RouteBuilder{
#Override
public void configure() throws Exception {
from("rabbitmq://rabbit_host:port/Rabbit_Exchange")
.setHeader("CamelHttpMethod", constant("POST"))
.setHeader("Content-Type", constant("application/json"))
.bean(TransformResponse.class, "transform");
from("direct:transformedResponses")
.to("http4://rest_service_host:port/MyRestService");
}
}
And then in your transform bean, you can use camel Producer Template to publish the transformed payload(s) to your new direct endpoint (assuming you are using json?).
producerTemplate.sendBody("direct:transformedResponses", jsonString);
I am playing around with Camel for the first time. My trial project is to write an application that receives an HTTP GET request (using Jetty) and passes on the request via Thrift to another server. The answer received is then passed back to the client. (i.e. I'm writing a data switch or a middleware application if you will between an http-get request and a Thrift-enanbled server.)
I have the non-camel version perfectly and I am now trying to put the camel-equivalent together. For now I am only trying to get the jetty request written to a file.
This is what I have so far:
public class CamelMedicalService {
public static void main(String[] args) throws Exception {
CamelContext context = new DefaultCamelContext();
context.addRoutes(new MedicalServiceRouteBuilder());
context.start();
// Wait a minute and then stop all.
Thread.sleep(TimeUnit.MINUTES.toMillis(1));
context.stop();
}
}
and the RouteBuilder:
public class MedicalServiceRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
from("jetty:http://localhost:8080").to("file://test");
}
}
I am currently getting
java.lang.ClassNotFoundException: org.eclipse.jetty.util.component.Destroyable... I am not sure how to resolve this. How should I set this up so that I can receive an http request and pass it to the file?
Like in the comments, please check if jetty-util.jar is in the classpath, if not, you can copy it to your WEB-INF/lib directory.