The uRouting (micro routing) API is a simple routing API in a JAXRS fashion optimized at compile time. It aims to provide a friendly and useful routing mechanism to developers, simplifying the way you used to create RESTFul endpoints with Undertow's API.
The router is the component that translates each incoming HTTP request to a method call. In this documentation we conventionally call these methods route
. The main Kikaha's micro Routing mechanism is defined by the kikaha-urouting
module, which you can include on your classpath by adding the following snippet on your pom.xml
file.
<dependency>
<groupId>io.skullabs.kikaha</groupId>
<artifactId>kikaha-urouting</artifactId>
</dependency>
The micro Routing is a compile time generated layer that binds your controller method to the Undertow's low-level API. It means that, under the hood, a class that implements io.undertow.server.HttpHandler
is generated to call your route method. The micro routing was developed to be simple and with low (or almost no) overhead. Its API was strongly inspired by JAX-RS, Play Framework and Scalatra, both awesome and powerful technologies that together cover most part of JVM's web developers community.
While writing a Kikaha route, you will probably notice that it have a basic structure (a convention) we should follow.
@HTTP_METHOD @Path( PATH )
RESPONSE nameOfThisRoute( PARAMETERS ){
...
}
Where the UPPERCASE words above means:
Every time you write a uRouting Route, Kikaha will:
When you are coding you should also be aware that:
@kikaha.urouting.api.Path
.@kikaha.urouting.api.Produces
statically defines which is the Content-Type you intent to return@kikaha.urouting.api.Consumes
statically defines which is the Content-Type you intent to receive an object from a requestTo return a response to the HTTP client you should basically return any object you want on your route method.
The following code will serialize the User
object and send it back to the HTTP Client.
@POST @Path("users")
User sendUser(){
return new User( "Jay", "Milagroso", 1l );
}
But, what if you want to send a more specific response, including Cookies or HTTP Headers?
In this case, you better return any object that implements a specific Response Type Interface kikaha.urouting.api.Response
. The kikaha.urouting.api.DefaultResponse
has a lot of methods which could helps you on the most common cases. Bellow there's an example that will send the same User
object, but including a few headers.
@POST @Path("users")
Response sendUser(){
return DefaultResponse.ok()
.entity( new User( "Jay", "Milagroso", 1l ) )
.header( "Access-Control-Allow-Origin", "*" );
}
DefaultResponse
is the main implementation of Response
interfaceUndertow have a very optimized API and one of the reasons it performs so well is because of fact it handles requests in a reactive way, allowing developers to take advantage from modern frameworks which uses Asynchronous APIs. Kikaha have abstracted this behavior on uRouting API through the AsyncResponse object.
Unlike synchronous routes, asynchronous routes should not have return objects. Also, it expects a special object (AsyncResponse) to be "injected" on the parameter list. Let's see an example:
@POST @Path("users")
void sendUser( @Context AsyncResponse asyncResp ){
// myAsyncDB is a hypothetical object that retrieves an user asynchronously.
myAsyncDB.retrieveUser( user -> {
Response resp = DefaultResponse.ok().entity( user ).header( "Access-Control-Allow-Origin", "*" );
asyncResp .write( resp )
});
}
As we can see on the above code, it uses AsyncResponse
object to send the response once it receives the response from our hypothetical myAsyncDB client.
The following HTTP methods Kikaha is able to handle:
kikaha.urouting.api.GET
kikaha.urouting.api.DELETE
kikaha.urouting.api.POST
kikaha.urouting.api.PUT
kikaha.urouting.api.PATCH
The sample bellow shows how is possible to handle a POST/PUT request with micro Routing API.
public class UserResource {
// the stored data
final Set<User> users = new HashSet<>();
// handling both PUT and POST to simplify the example
@POST @PUT
@Path( "users" )
@Consumes("application/json")
// once it does not return a response, the default response is 204
public void addUser( User user ){
users.add( user );
}
}
Yes, you are able to handle requests in a similar as JAXRS does. Bellow is an example showing how you can retrieve a user by its id. In this case, the id is placed as a path parameter inside the URI path you defined at @kikaha.urouting.api.Path
annotation.
public class UserResource {
// the stored data
final Map<Long, User> users = new HasmMap<>();
@GET
@Path( "users/{id}" )
@Produces("application/json")
public User retrieveUserById( @PathParam("id") Long id ){
return users.get( id );
}
}
@kikaha.urouting.api.PathParam
was used to extract the "id" parameter from the path. There is also more information you can retrieve from the request using pre-defined annotation from micro Routing API. Bellow are some examples:@kikaha.urouting.api.QueryParam( "id" )
: Retrieves the sent query parameters named "id"@kikaha.urouting.api.CookieParam( "SESSION_ID" )
: Retrieves the first sent cookie named "SESSION_ID"@kikaha.urouting.api.HeaderParam( "X-Token" )
: Retrieves the first send header named "X-Token".@kikaha.urouting.api.Context
.All Kikaha's uRouting route is automatically deployed as an injectable service. In practice, it means that, if you are using the CDI module, you could inject any dependency through the @javax.inject.Inject
annotation, even if you does not annotate the class with the @javax.inject.Singleton
annotation.
In every request received Undertow stores the request data (headers, cookies, query parameters, etc) in an instance of HttpServerExchange
object. This object was designed to provide any useful data while the request is happening (request context only). You cannot access this informations outside of a request.
Sometimes you need to extract some meta information from your requests, like Autentication Tokens, the current Logged In user, the current Tenant, etc. It can become a repetitive task if you have many routing endpoints. Kikaha allows developers to inject data retrieved by the request context into your routing methods.
package sample;
import kikaha.urouting.api.*;
import io.undertow.security.idm.Account;
public class UserResource {
@POST
public void persistUser( @Context Account account, User user ){
// do something with the logged in account
}
}
The above example code shows how is possible to inject the current logged in account in the persistUser
method. Note that the @kikaha.urouting.api.Context
annotation was used to inject the io.undertow.security.idm.Account
object extracted from the Undertow's request context object (HttpServerExchange).
Out of box, Kikaha allows developers to inject the following data extracted from Request Context:
io.undertow.security.idm.Account
: the Undertow implementation of current logged in userio.undertow.server.HttpServerExchange
: the object that contains all data available at request contextio.undertow.server.handlers.form.FormData
: Undertow implementation used to represent a Form Data received by the HTTP clientio.undertow.security.api.SecurityContext
: Undertow implementation used to represent the security context from the current request.kikaha.core.security.SecurityContext
: A Kikaha specific implementation of Undertow's io.undertow.security.api.SecurityContext. It has an improved API and offers a more convenient approach to deal with the current session.kikaha.core.security.Session
: The current logged in session. It allows developers to store data into the current session.Developers are encouraged to create their own kikaha.urouting.api.ContextProducer
every time is needed to inject meta-data received from current request context.
// TenantContextProducer.java
package sample;
import kikaha.urouting.api.*;
import javax.inject.*;
@Singleton
public class TenantContextProducer implements ContextProducer<Tenant> {
@Override
public Tenant produce( HttpServerExchange exchange ) throws RoutingException {
String hostAndPort = exchange.getHostAndPort()
return new Tenant( hostAndPort );
}
}
// Tenant.java
public class Tenant {
final private String hostAndPort;
public Tenant( String hostAndPort ){
this.hostAndPort = hostAndPort;
}
public String toString(){
return hostAndPort;
}
}
// UserResource.java
@Path("api/users")
public class UserResource {
@GET
@Path("{userId}")
public User retrieveUserById(
@Context Tenant tenant, @PathParam("userId") Long id ){
// do something
}
}
The above sample implementation shows how is possible to inject a Tenant object (representing which is the tenant of current request) in the routing method. At this example, we are assuming that the Tenant Id is the current sub-domain, like mycustomer.mydomain.com
, but it is possible to retrieve this information from dynamic sources like databases or some cache mechanisms like **Hazelcast **.
The sample code bellow shows how to handle an upload request with 'multipart/form-data' content type.
@Path("api/pdf/upload")
public class PDFUploadResource {
@MultiPartFormData
public void handleUpload( File file ){
System.out.println( file.getName() );
}
}
The File file parameter, that represents the uploaded file, can be altogether if other special parameters like @PathParam
or @Context
annotated attributes.
@Singleton
public class UserService {
@Inject
EntityManager em;
@GET @Path("users")
public Iterable<User> retrieveUsers(){
Query query = em.createQuery("SELECT e FROM User e");
return (Iterable<User>) query.getResultList();
}
}
Consider the above sample code. The retrieveUsers
is a very straightforward method to retrieve all persisted User
s found in the database. But, what would happen if the method throws javax.persistence.QueryTimeoutException
. You probably will try to handle this exception with a try/catch block. It wouldn't be a big deal until this behavior starts to repeat in every method that uses Query.getResultList()
method.
To avoid repetitive coding style handling exceptions, you can define a global Exception Handler. It allows you to define custom HTTP responses when specific exceptions are thrown. The following sample codes illustrates how to handle the QueryTimeoutException
.
import kikaha.urouting.api.*;
import javax.inject.*;
@Singleton
public class QueryTimeoutExceptionHandler
implements ExceptionHandler<QueryTimeoutException> {
public Response handle( QueryTimeoutException cause ){
return DefaultResponse.serverError( cause.getMessage() );
}
}
Note that we should make
QueryTimeoutExceptionHandler
injectable, in order be automatically discovered by Kikaha's start up routine. That's the reason we put the@Singleton
annotation into that class.
What if you are designing an application that will receive data in an uncommon content-type like CVS or even XLS? Kikaha allows developers to include new mechanisms to handle and write back data in any formats (Content-Type) he needs. These mechanisms are called Serializers and Unserializers.
Unserializers are mechanisms designed to convert incomming data into another (more useful) format/object. Every time you make a POST request and send an entity as body, Kikaha uses the Content-Type header to detect which Unserializer
will handle this request and convert it into an object that represents your sent entity. The bellow code illustrates how you can handle this POST request.
package sample;
import kikaha.urouting.api.*;
import javax.inject.*;
@Path("api/users")
public class User {
@POST
@Consumes( "text/csv" )
public void persistUser( User user ){
// call user persistence here
}
}
If the HTTP client which is sending the request informs a Content-Type, it will be used to identify which kikaha.urouting.api.Unserializer
implementation will be used to convert the sent data into User
. If the Content-Type header is missing, the fallback Content-Type defined by the @Consumes
annotation will be used.
Out-of-box, Kikaha only supports PLAIN TEXT Content-Types. There is a module called kikaha-urouting-jackson
and kikaha-urouting-jaxb
that add JSON and XML support respectively. Bellow you can see how you can create your own CSV unserializer.
import kikaha.core.modules.http.ContentType;
import kikaha.urouting.api.*;
import javax.inject.*;
@Singleton
@ContentType("text/csv")
public class CSVUnserializer implements Unserializer {
public <T> T unserialize( final HttpServerExchange input, final Class<T> targetClass, String encoding ) throws IOException {
// You can use the 'input' object to unserialize the entity
// Or import the Simplified Undertow API
T instance = // apply your unserialization rule here
return instance;
}
}
Serializers, in other hand, are responsible to convert an Java object into data that will be send back to the HTTP client. Every time you need retrieve a list of users from your database and send it back to the browser, Kikaha will use the Content-Type returned by Response.contentType()
to determine which kikaha.urouting.api.Serializer
implementation will be used to translate the Java list of objects into a data that the browser understand. The method retrieveUsers
bellow illustrates this.
package sample;
import kikaha.urouting.api.*;
import javax.inject.*;
@Path("api/users")
public class UserResource {
// Inject here the services you need to retrieve data...
@GET
public Response retrieveUsers(){
List<User> users = // retrieve the list of users
return DefaultResponses.ok( users )
.contentType( "text/csv" );
}
@GET
@Path("alternative1")
@Produces("text/csv")
public Response retrieveUsersAlternative1(){
List<User> users = // retrieve the list of users
return DefaultResponses.ok( users );
}
@GET
@Path("alternative2")
@Produces("text/csv")
public List<User> retrieveUsersAlternative2(){
List<User> users = // retrieve the list of users
return users;
}
}
The above sample code also illustrate two alternative ways to send back the user list to the HTTP cliente. The retrieveUsersAlternative1
shows that is possible to define the produced response Content-Type through the @Produces
annotation. The retrieveUsersAlternative2
shows that you can just return the user list object, also indicating the produced response Content-Type through the @Produces
annotation.
In both cases (serialization and unserialization) when no Content-Type is defined "text/plain" is assumed. Thus, developers are encouraged always inform the desired Content-Type for each exposed endpoint, or at the class definition
WELCOME About Kikaha philosophy
GETTING STARTED Getting started in 1 minute Creating a Kikaha maven project Architecture overview
TUTORIALS Logging configuration Configuring the server Creating your first HTTP route Kikaha's command line interface Configuring your favorite IDE Wro4j Integration
CORE FEATURES HTTP and HTTPS Routing static assets Dependency injection Authentication and authorization Smart routes
ESSENTIAL MODULES μRouting API WebSocket Routing Database Connection Pool JSON with Jackson Protobuf Mustache Templates Rocker Templates BCrypt
CLOUD MODULES Overview of Cloud Modules Consul.io Codahale's Metrics Auth0 Single Sign-On μWorkers - Actor-like API Hazelcast
AWS-RELATED MODULES Overview of AWS-Related Modules Deploying Applications on AWS AWS IAM Credentials AWS EC2 AWS SQS queues AWS CloudWatch metrics AWS Application Load Balancer AWS Lambda functions AWS X-Ray
ADVANCED TOPICS Creating custom modules Routing with Undertow's API Creating custom cloud modules