4.2. Defining Services with REST DSL
REST DSL is a facade
The REST DSL is effectively a facade that provides a simplified syntax for defining REST services in a Java DSL or an XML DSL (Domain Specific Language). REST DSL does not actually provide the REST implementation, it is just a wrapper around an existing REST implementation (of which there are several in Apache Camel).
Advantages of the REST DSL
The REST DSL wrapper layer offers the following advantages:
- A modern easy-to-use syntax for defining REST services.
- Compatible with multiple different Apache Camel components.
- Swagger integration (through the
camel-swagger
component).
Components that integrate with REST DSL
Because the REST DSL is not an actual REST implementation, one of the first things you need to do is to choose a Camel component to provide the underlying implementation. The following Camel components are currently integrated with the REST DSL:
- Servlet component (
camel-servlet
). - Spark-Rest component (
camel-spark-rest
). - Netty HTTP component (
camel-netty-http
). - Netty4 HTTP component (
camel-netty4-http
). - Jetty component (
camel-jetty
). - Restlet component (
camel-restlet
).
Note
The Rest component (part of
camel-core
) is not a REST implementation. Like the REST DSL, the Rest component is a facade, providing a simplified syntax to define REST services using a URI syntax. The Rest component also requires an underlying REST implementation.
Configuring REST DSL to use a REST implementation
To specify the REST implementation, you use either the
restConfiguration()
builder (in Java DSL) or the restConfiguration
element (in XML DSL). For example, to configure REST DSL to use the Spark-Rest component, you would use a builder expression like the following in the Java DSL:
restConfiguration().component("spark-rest").port(9091);
And you would use an element like the following (as a child of
camelContext
) in the XML DSL:
<restConfiguration component="spark-rest" port="9091"/>
Syntax
The Java DSL syntax for defining a REST service is as follows:
rest("BasePath").Option()+. .Verb("Path").Option()+.[to() | route().CamelRoute.endRest()] .Verb("Path").Option()+.[to() | route().CamelRoute.endRest()] ... .Verb("Path").Option()+.[to() | route().CamelRoute];
Where
CamelRoute
is an optional embedded Camel route (defined using the standard Java DSL syntax for routes).
The REST service definition starts with the
rest()
keyword, followed by one or more verb clauses that handle specific URL path segments. The HTTP verb can be one of get()
, head()
, put()
, post()
, delete()
, or verb()
. Each verb clause can use either of the following syntaxes:
- Verb clause ending in
to()
keyword. For example:get("...").Option()+.to("...")
- Verb clause ending in
route()
keyword (for embedding a Camel route). For example:get("...").Option()+.route("...").CamelRoute.endRest()
REST DSL with Java
In Java, to define a service with the REST DSL, put the REST definition into the body of a
RouteBuilder.configure()
method, just like you do for regular Apache Camel routes. For example, to define a simple Hello World service using the REST DSL with the Spark-Rest component, define the following Java code:
restConfiguration().component("spark-rest").port(9091); rest("/say") .get("/hello").to("direct:hello") .get("/bye").to("direct:bye"); from("direct:hello") .transform().constant("Hello World"); from("direct:bye") .transform().constant("Bye World");
The preceding example features three different kinds of builder:
-
restConfiguration()
- Configures the REST DSL to use a specific REST implementation (Spark-Rest).
-
rest()
- Defines a service using the REST DSL. Each of the verb clauses are terminated by a
to()
keyword, which forwards the incoming message to adirect
endpoint (thedirect
component splices routes together within the same application). -
from()
- Defines a regular Camel route.
REST DSL with XML
In XML, to define a service with the XML DSL, define a
rest
element as a child of the camelContext
element. For example, to define a simple Hello World service using the REST DSL with the Spark-Rest component, define the following XML code (in Blueprint):
<camelContext xmlns="http://camel.apache.org/schema/blueprint"> <restConfiguration component="spark-rest" port="9091"/> <rest path="/say"> <get uri="/hello"> <to uri="direct:hello"/> </get> <get uri="/bye"> <to uri="direct:bye"/> </get> </rest> <route> <from uri="direct:hello"/> <transform> <constant>Hello World</constant> </transform> </route> <route> <from uri="direct:bye"/> <transform> <constant>Bye World</constant> </transform> </route> </camelContext>
Specifying a base path
The
rest()
keyword (Java DSL) or the path
attribute of the rest
element (XML DSL) allows you to define a base path, which is then prefixed to the paths in all of the verb clauses. For example, given the following snippet of Java DSL:
rest("/say")
.get("/hello").to("direct:hello")
.get("/bye").to("direct:bye");
Or given the following snippet of XML DSL:
<rest path="/say">
<get uri="/hello">
<to uri="direct:hello"/>
</get>
<get uri="/bye" consumes="application/json">
<to uri="direct:bye"/>
</get>
</rest>
The REST DSL builder gives you the following URL mappings:
/say/hello /say/bye
The base path is optional. If you prefer, you could (less elegantly) specify the full path in each of the verb clauses:
rest() .get("/say/hello").to("direct:hello") .get("/say/bye").to("direct:bye");
Using Dynamic To
The REST DSL supports the
toD
dynamic to parameter. Use this parameter to specify URIs.
For example, in JMS a dynamic endpoint URI could be defined in the following way:
public void configure() throws Exception { rest("/say") .get("/hello/{language}").toD("jms:queue:hello-${header.language}"); }
In XML DSL, the same details would look like this:
<rest uri="/say"> <get uri="/hello//{language}"> <toD uri="jms:queue:hello-${header.language}"/> </get> <rest>
For more information about the
toD
dynamic to parameter, see the section called “Dynamic To”.
URI templates
In a verb argument, you can specify a URI template, which enables you to capture specific path segments in named properties (which are then mapped to Camel message headers). For example, if you would like to personalize the Hello World application so that it greets the caller by name, you could define a REST service like the following:
rest("/say") .get("/hello/{name}").to("direct:hello") .get("/bye/{name}").to("direct:bye"); from("direct:hello") .transform().simple("Hello ${header.name}"); from("direct:bye") .transform().simple("Bye ${header.name}");
The URI template captures the text of the
{name}
path segment and copies this captured text into the name
message header. If you invoke the service by sending a GET HTTP Request with the URL ending in /say/hello/Joe
, the HTTP Response is Hello Joe
.
Embedded route syntax
Instead of terminating a verb clause with the
to()
keyword (Java DSL) or the to
element (XML DSL), you have the option of embedding an Apache Camel route directly into the REST DSL, using the route()
keyword (Java DSL) or the route
element (XML DSL). The route()
keyword enables you to embed a route into a verb clause, with the following syntax:
RESTVerbClause.route("...").CamelRoute.endRest()
Where the
endRest()
keyword (Java DSL only) is a necessary punctuation mark that enables you to separate the verb clauses (when there is more than one verb clause in the rest()
builder).
For example, you could refactor the Hello World example to use embedded Camel routes, as follows in Java DSL:
rest("/say") .get("/hello").route().transform().constant("Hello World").endRest() .get("/bye").route().transform().constant("Bye World");
And as follows in XML DSL:
<camelContext xmlns="http://camel.apache.org/schema/blueprint"> ... <rest path="/say"> <get uri="/hello"> <route> <transform> <constant>Hello World</constant> </transform> </route> </get> <get uri="/bye"> <route> <transform> <constant>Bye World</constant> </transform> </route> </get> </rest> </camelContext>
Note
If you define any exception clauses (using
onException()
) or interceptors (using intercept()
) in the current CamelContext
, these exception clauses and interceptors are also active in the embedded routes.
Specifying the content type of requests and responses
You can filter the content type of HTTP requests and responses using the
consumes()
and produces()
options in Java, or the consumes
and produces
attributes in XML. For example, some common content types (officially known as Internet media types) are the following:
text/plain
text/html
text/xml
application/json
application/xml
The content type is specified as an option on a verb clause in the REST DSL. For example, to restrict a verb clause to accept only
text/plain
HTTP requests, and to send only text/html
HTTP responses, you would use Java code like the following:
rest("/email") .post("/to/{recipient}").consumes("text/plain").produces("text/html").to("direct:foo");
And in XML, you can set the
consumes
and produces
attributes, as follows:
<camelContext xmlns="http://camel.apache.org/schema/blueprint"> ... <rest path="/email"> <post uri="/to/{recipient}" consumes="text/plain" produces="text/html"> <to "direct:foo"/> </get> </rest> </camelContext>
You can also specify the argument to
consumes()
or produces()
as a comma-separated list. For example, consumes("text/plain, application/json")
.
Additional HTTP methods
Some HTTP server implementations support additional HTTP methods, which are not provided by the standard set of verbs in the REST DSL,
get()
, head()
, put()
, post()
, delete()
. To access additional HTTP methods, you can use the generic keyword, verb()
, in Java DSL and the generic element, verb
, in XML DSL.
For example, to implement the TRACE HTTP method in Java:
rest("/say") .verb("TRACE", "/hello").route().transform();
Where
transform()
copies the body of the IN message to the body of the OUT message, thus echoing the HTTP request.
To implement the TRACE HTTP method in XML:
<camelContext xmlns="http://camel.apache.org/schema/blueprint"> ... <rest path="/say"> <verb uri="/hello" method="TRACE"> <route> <transform/> </route> </get> </camelContext>
Defining custom HTTP error messages
If your REST service needs to send an error message as its response, you can define a custom HTTP error message as follows:
- Specify the HTTP error code by setting the
Exchange.HTTP_RESPONSE_CODE
header key to the error code value (for example,400
,404
, and so on). This setting indicates to the REST DSL that you want to send an error message reply, instead of a regular response. - Populate the message body with your custom error message.
- Set the
Content-Type
header, if required. - If your REST service is configured to marshal to and from Java objects (that is,
bindingMode
is enabled), you should ensure that theskipBindingOnErrorCode
option is enabled (which it is, by default). This is to ensure that the REST DSL does not attempt to unmarshal the message body when sending the response.For more details about object binding, see Section 4.3, “Marshalling to and from Java Objects”.
The following Java example shows how to define a custom error message:
// Java // Configure the REST DSL, with JSON binding mode restConfiguration().component("restlet").host("localhost").port(portNum).bindingMode(RestBindingMode.json); // Define the service with REST DSL rest("/users/") .post("lives").type(UserPojo.class).outType(CountryPojo.class) .route() .choice() .when().simple("${body.id} < 100") .bean(new UserErrorService(), "idTooLowError") .otherwise() .bean(new UserService(), "livesWhere");
In this example, if the input ID is a number less than 100, we return a custom error message, using the
UserErrorService
bean, which is implemented as follows:
// Java public class UserErrorService { public void idTooLowError(Exchange exchange) { exchange.getIn().setBody("id value is too low"); exchange.getIn().setHeader(Exchange.CONTENT_TYPE, "text/plain"); exchange.getIn().setHeader(Exchange.HTTP_RESPONSE_CODE, 400); } }
In the
UserErrorService
bean we define the custom error message and set the HTTP error code to 400
.
Parameter Default Values
Default values can be specified for the headers of an incoming Camel message.
You can specify a default value by using a key word such as
verbose
on the query parameter. For example, in the code below, the default value is false
. This means that if no other value is provided for a header with the verbose
key, false
will be inserted as a default.
rest("/customers/") .get("/{id}").to("direct:customerDetail") .get("/{id}/orders") .param() .name("verbose") .type(RestParamType.query) .defaultValue("false") .description("Verbose order details") .endParam() .to("direct:customerOrders") .post("/neworder").to("direct:customerNewOrder");
Wrapping a JsonParserException in a custom HTTP error message
A common case where you might want to return a custom error message is in order to wrap a
JsonParserException
exception. For example, you can conveniently exploit the Camel exception handling mechanism to create a custom HTTP error message, with HTTP error code 400, as follows:
// Java onException(JsonParseException.class) .handled(true) .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(400)) .setHeader(Exchange.CONTENT_TYPE, constant("text/plain")) .setBody().constant("Invalid json data");
REST DSL options
In general, REST DSL options can be applied either directly to the base part of the service definition (that is, immediately following
rest()
), as follows:
rest("/email").consumes("text/plain").produces("text/html")
.post("/to/{recipient}").to("direct:foo")
.get("/for/{username}").to("direct:bar");
In which case the specified options apply to all of the subordinate verb clauses. Or the options can be applied to each individual verb clause, as follows:
rest("/email") .post("/to/{recipient}").consumes("text/plain").produces("text/html").to("direct:foo") .get("/for/{username}").consumes("text/plain").produces("text/html").to("direct:bar");
In which case the specified options apply only to the relevant verb clause, overriding any settings from the base part.
Table 4.1, “REST DSL Options” summarizes the options supported by the REST DSL.
Java DSL | XML DSL | Description |
---|---|---|
bindingMode() | @bindingMode | Specifies the binding mode, which can be used to marshal incoming messages to Java objects (and, optionally, unmarshal Java objects to outgoing messages). Can have the following values: off (default), auto , json , xml , json_xml . |
consumes() | @consumes | Restricts the verb clause to accept only the specified Internet media type (MIME type) in a HTTP Request. Typical values are: text/plain , text/http , text/xml , application/json , application/xml . |
customId() | @customId | Defines a custom ID for JMX management. |
description() | description | Document the REST service or verb clause. Useful for JMX management and tooling. |
enableCORS() | @enableCORS | If true , enables CORS (cross-origin resource sharing) headers in the HTTP response. Default is false . |
id() | @id | Defines a unique ID for the REST service, which is useful to define for JMX management and other tooling. |
method() | @method | Specifies the HTTP method processed by this verb clause. Usually used in conjunction with the generic verb() keyword. |
outType() | @outType | When object binding is enabled (that is, when bindingMode option is enabled), this option specifies the Java type that represents a HTTP Response message. |
produces() | produces | Restricts the verb clause to produce only the specified Internet media type (MIME type) in a HTTP Response. Typical values are: text/plain , text/http , text/xml , application/json , application/xml . |
type() | @type | When object binding is enabled (that is, when bindingMode option is enabled), this option specifies the Java type that represents a HTTP Request message. |
VerbURIArgument | @uri | Specifies a path segment or URI template as an argument to a verb. For example, get(VerbURIArgument) . |
BasePathArgument | @path | Specifies the base path in the rest() keyword (Java DSL) or in the rest element (XML DSL). |