Monday, December 23, 2019

Apache Camel + Spring Boot: Different components to expose HTTP endpoints

Apache Camel is an open source integration framework that allows you to integrate technologically diverse systems using a large library of components. A common use-case is to service HTTP based endpoints. Those of course come in several flavors and there is quite a choice in components to use.

In this blog post I'll take a look at what is available and how they differ with respect to flexibility to define multiple hosts, ports and URLs to host from using a single CamelContext. Depending on your use-case you will probably be using one of these. You can find my sample project here.

Components

REST DSL

The REST DSL is not an actual component but can be considered a wrapper for several components. It has integration with for example the Swagger module to generate documentation. It uses a RestConfiguration which can only occur once in a CamelContext (when using Spring Boot in combination with Apache Camel). The RestConfiguration specifies things like the base URL, the port and the component used as consumer / HTTP server.

Although easy to use, it is also limiting in that only a single RestConfiguration can be used within a CamelContext and that using multiple CamelContexts within a single application is not supported (see here). Thus this will not allow you to host services on different ports from the same Spring Boot / Apache Camel application. Also it is not allowed to define the same base path for different services in two different component-routes. You can run on a different port then the base Apache Camel / Spring Boot servlet engine though.

restConfiguration().component("netty-http").host("0.0.0.0").port(8084);
rest("/url1").get().id("test_route1").to("log:dummylog");

restConfiguration().component("netty-http").host("0.0.0.0").port(8085);
rest("/url2").get().id("test_route2").to("log:dummylog");

A small example of using the REST DSL above. Mind that both services will be run on port 8085 since there is only one restConfiguration! The REST DSL is powerful since it wraps several other components. If I want to switch to for example jetty instead of netty-http, I only have to change the component specification in the REST configuration and no other code (if I made sure not to use implementation specific features of the different components). You can also externalize the component specification to make switching your HTTP server as simple as changing a property file.

You can find the documentation here and an example on how to use it here.

REST

The REST component is quite powerful. It allows you to easily expose REST endpoints using various other components which implement the org.apache.camel.spi.RestConsumerFactory such as the http, undertow, servlet, netty-http, spark-rest and jetty. Also it has a flexible URI syntax. It integrates tightly with the REST DSL.

If you do not want to use the RestConfiguration class, you can directly configure the component from the CamelContext. The RestComponent however does not have a setPort method. I could not find a quick way to create multiple rest components running on different ports. This is similar as to using REST DSL, see below.

RestComponent sc = getContext().getComponent("rest",RestComponent.class);
sc.setConsumerComponentName("jetty");
from("rest:get:/:url1").id("test_route1").to("log:dummylog");

Mind that you explicitly need to specify the component to use if it is not present in the RestConfiguration and you have multiple components providing the RestConsumerFactory on your classpath. Else you will encounter the following error:

Caused by: java.lang.IllegalArgumentException: Multiple RestConsumerFactory found on classpath. Configure explicit which component to use

HTTP

The camel-http component (in Apache Camel 2.x use camel-http4 instead) allows you to consume HTTP services. It does not allow you to host services with. For that the jetty component is recommended.

Jetty

You can use the Jetty component to create multiple routes with the same or different ports and specify different URLs for the same port in different routes.

The below example will work.
from("jetty:http://0.0.0.0:8083/url1").id("test_route1").to("log:dummylog");
from("jetty:http://0.0.0.0:8083/url2").id("test_route2").to("log:dummylog");

Jetty has the challenge that Jetty libraries are regular inhabitants of most application servers. If you want the classes from your deployment to be used instead of the (often outdated) application server versions, you might need to manipulate dependencies and class loading (create web.xml files, deployment descriptors, etc).

Servlet

The servlet component allows you to host services at different endpoints, however you don't have the option to configure different ports or URLs outside of the path specified in the property camel.component.servlet.mapping.context-path and server-port in application.properties when using the Apache Camel Servlet Spring Boot starter. You can not easily run on a different port then the base Apache Camel / Spring Boot servlet engine.

//Both OK, however path below camel.component.servlet.mapping.context-path and uses the same port
from("servlet:/url1").id("test_route1").to("log:dummylog");
from("servlet:/url2").id("test_route2").to("log:dummylog");

Netty-http

The Netty-http component facilitates HTTP transport using the Netty component. The Netty component is low-level and socket based while netty-http allows you to easily do HTTP with Netty. Netty-http allows you to use the same port in different routes as long as the NettyServerBootstrapConfiguration is the same. This means the configuration of the route is the same; they use the same parameters in the URI. This creates in my opinion a lot of flexibility.

In the below example you can see that hosting on the same port with different URLs works without challenges.

The relevant code:
from("netty-http:http://0.0.0.0:8083/url1").id("test_route1").to("log:dummylog");
from("netty-http:http://0.0.0.0:8083/url2").id("test_route2").to("log:dummylog");


Undertow

The Undertow component allows you to host HTTP and WebSocket endpoints.

from("undertow:http://0.0.0.0:8083/myapp1").id("test_route1").to("log:dummylog");
from("undertow:http://0.0.0.0:8084/myapp2").id("test_route2").to("log:dummylog");

Using Undertow has the benefit of not conflicting with most application server libraries (such as WebLogic) if you want to deploy your Apache Camel / Spring Boot application to one of those.

Spark REST

The Spark REST component and Spark Framework have nothing to do with Apache Spark!
If you want to interface with Apache Spark, you should use the Spark component. The Spark REST component allows you to use  Spark Framework. This is a micro framework for creating web applications in Kotlin and Java 8. Mind that the support for Java 8 in Apache Camel 3.x is best effort; you should be running Apache Camel 3.x on Java 11!

It uses Jetty and provides an alternative syntax to the REST DSL. You can however use the REST DSL instead of Spark but still use the Spark REST component. In that case you can still obtain the raw Spark request by using the getRequest method in org.apache.camel.component.sparkrest.SparkMessage and still do Spark specific things with that.

Supplying the port option in the URI did not work:

Caused by: org.apache.camel.ResolveEndpointFailedException: Failed to resolve endpoint: spark-rest://get:url1?port=8083 due to: There are 1 parameters that couldn't be set on the endpoint. Check the uri if the parameters are spelt correctly and that they are properties of the endpoint. Unknown parameters=[{port=8083}]

I discovered the port is a property of the Component instance and not the Endpoint. The Component instance can be obtained from the CamelContext. However, since there is a single Component instance of spark-rest in the CamelContext, you can only supply a single port. Using the RestConfiguration for this of course does not help either since there is also only one instance of that class available in the CamelContext. A workaround for this is adding more components to the CamelContext with different names and a different configuration.

SparkComponent sc = getContext().getComponent("spark-rest",SparkComponent.class);
sc.setPort(8083);

SparkConfiguration sc2config = sc.getSparkConfiguration();
SparkComponent sc2 = new SparkComponent();
sc2.setSparkConfiguration(sc2config);
sc2.setPort(8084);
getContext().addComponent("spark-rest2",sc2);

from("spark-rest://get:url1").id("test_route1").to("log:dummylog");
from("spark-rest2://get:url2").id("test_route2").to("log:dummylog");

This way you can run on multiple ports/URLs using a distinct Component instance per port. This code is however Spark specific and if you want to switch to for example plain Jetty, you have to change it. Also there is the application server challenge since it uses Jetty.

Finally

Summary

Please mind there are many other considerations for choosing a specific component. You can think of specific features like WebSocket support, performance, security related features, blocking (jetty, spark, servlet) or non-blocking (netty, undertow) nature, resource consumption, etc.

The below table summarizes my findings on components which can be used to run as Apache Camel HTTP server within Spring Boot. Red means no (no no expected issues is a good thing ;), green means yes, yellow means 'it depends' or 'not easily'. Undertow comes out quite nicely as being a flexible component!


Apache Camel 2.x vs Apache Camel 3.x

The differences between Camel 2.x and Camel 3.x are well described in the migration guide. Two things I directly encountered are listed below.

groupId of artifacts

In Apache Camel 2.x most of the Spring Boot related artifacts such as the starters are present under groupId org.apache.camel. For Apache Camel 3.x the documentation specifies org.apache.camel.springboot as the groupId. Here only artifacts for the 3.x version are present.

netty4-http and camel-http4

Apache Camel 2.x has netty-http which is deprecated and netty4-http which is a newer version of the component which should be the one to use. For the camel-http component it is similar. Don't use it, but use camel-http4. In Apache Camel 3.x however there is is no '4' version of both components and just netty-http and http. Thus for Apache Camel 2.x you should use netty4-http and camel-http4 while for Apache Camel 3.x you should use netty-http and http. This is also something to mind when migrating.

No comments:

Post a Comment