Camel+CDI to make an HTTP proxy with Meecrowave
Apache Camel has got a decent CDI extension some years ago now, let's see how simple it is to get started to use it to make an HTTP proxy with the microserver Apache Meecrowave.
The first step to create a proxy with Camel and Meecrowave is to import some dependencies. To implement it, we will rely on Tomcat for the HTTP layer (provided by Meecrowave stack) and therefore we will use camel-servlet module and for the proxying (client) we will use HttpClient4 so camel-http4:
<dependencies>
<dependency>
<groupId>org.apache.meecrowave</groupId>
<artifactId>meecrowave-core</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-cdi</artifactId>
<version>2.23.0</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-servlet</artifactId>
<version>2.23.0</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-http4</artifactId>
<version>2.23.0</version>
</dependency>
</dependencies>
You will note we added camel-cdi since we want to leverage the CDI stack Meecrowave brings instead of importing Spring or doing it in plan standalone.
Once it is done the next step to use camel-servlet is to bind the camel http transport (which is just a servlet). We can do it in several manners but to keep it simple we will just write a ServletContainerInitializer:
package com.github.rmannibucau.camel;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import org.apache.camel.component.servlet.CamelHttpTransportServlet;
public class ServletSetup implements ServletContainerInitializer {
@Override
public void onStartup(final Set<Class<?>> classes, final ServletContext ctx) {
final ServletRegistration.Dynamic servlet = ctx.addServlet(
"CamelServlet",CamelHttpTransportServlet.class);
servlet.setAsyncSupported(true);
servlet.setLoadOnStartup(1);
// servlet.setInitParameter("async", "true"); // optional
servlet.addMapping("/*");
}
}
Then to make it effective you can just register the fully qualified name of this class in META-INF/services/javax.servlet.ServletContainerInitializer:
com.github.rmannibucau.camel.ServletSetup
Now we can start by implementing a route builder proxying some endpoint:
import javax.enterprise.context.Dependent;
import org.apache.camel.builder.RouteBuilder;
@Dependent // <1>
public class ProxyDefinition extends RouteBuilder {
@Override
public void configure() {
from("servlet://*") // <2>
.to("http4://google.fr?bridgeEndpoint=true&throwExceptionOnFailure=false"); // <3>
}
}
- Since we are using camel-cdi and we didn't define a META-INF/beans.xml we will mark our routebuilder as a CDI bean thanks to @Dependent,
- we want to proxy all incoming requests so we will use the wildcard as a marker,
- we redirect any request to Google (in the context of this post I consider that Google is used widely enough to be simple to demonstrate the proxying)
Now if you run that route (can be as simple as mvn package meecrowave:run if you use Meecrowave Maven plugin) then you will see it will just fail because of the wildcard. The camel servlet matching is not the servlet matching and there is no real wildcard but only path element wildcard you can name (as in JAX-RS {name} placeholders). So does it mean we can't use servlet component to implement a proxy? of course not!
The trick is to set a custom ServletResolveConsumerStrategy in the transport servlet. In our case, all requests must resolve our "from" consumer, i.e. servlet:/* consumer. To set this implementation we must override the default camel servlet and set the strategy in init() after having called the super method otherwise it would reset our initialization (the parent init set the strategy). We just make our servlet container initializer evolving to look like:
public class ServletSetup implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) {
final CamelHttpTransportServlet transportServlet = new CamelHttpTransportServlet() {
@Override
public void init(final ServletConfig config) throws ServletException {
super.init(config);
// <1>
setServletResolveConsumerStrategy(new ServletResolveConsumerStrategy() {
@Override
public HttpConsumer resolve(final HttpServletRequest request,
final Map<String, HttpConsumer> consumers) {
return consumers.get("servlet:/*"); // <2>
}
@Override
public boolean isHttpMethodAllowed(final HttpServletRequest request, final String method,
final Map<String, HttpConsumer> consumers) {
return true;
}
});
}
};
final ServletRegistration.Dynamic servlet = ctx.addServlet("CamelServlet", transportServlet);
servlet.setAsyncSupported(true);
servlet.setLoadOnStartup(1);
// servlet.setInitParameter("async", "true"); // if you want to go with Servlet 3 async mode
servlet.addMapping("/*");
}
}
- This is where the magic happens and how we set a custom strategy we could call a SingleProxyStrategy,
- Since we want to be in proxy mode, we ensure any request is sent to the same consumer (our wildcard one).
Now if you recompile and restart your instance, you can go on http://localhost:8080 and you should see Google homepage. But does it really work? If you doubt, you can add /search?q=camel to the URL and you should see the search result.
All that to proxy google? Not really, this shows that it is very simple to build a proxy using the current servlet container as consumer/receiver and http4 (or any other camel client) as data retriever. Our route was trivial and without any value but it is a Camel route so it means you can very easily add any component/processor inside, even more easily that you can use CDI for your processor since we used camel-cdi! This opens the door to a lot of very advanced features like load balancing, rate limiting, throttling, enrichment (in headers or even the payload), transversal decryption of the payloads, caching of remote calls etc... All these features done in a very line of code and thanks to Camel flexibility. This is the dream for a microservice based architecture as you can mix transversal concerns with semi-business ones (for instance: an integration with Vault is not always trivial to make generic because it can be used in a lot of ways, but once you have your company choice, it is few lines to write).
From the same author:
In the same category: