Fork me on GitHub

Route are URL schema, which describe the interfaces for making requests to your web application. Combining an HTTP request method (a.k.a. HTTP verb) and a path pattern, you define URLs in your application.
Each route has an associated RouteHandler, which does the job of performing any action in the application and sending the HTTP response.
Routes are defined using an HTTP verb and a path pattern. Any request to the server that matches a route definition is routed to the associated route handler.

GET("/", new RouteHandler() {

    @Override
    public void handle(RouteContext routeContext) {
        routeContext.send("Hello World");
    }

});

// or more concise using Java 8 lambdas

GET("/", routeContext -> routeContext.send("Hello World"));

Routes in Pippo are created using methods named after HTTP verbs. For instance, in the previous example, we created a route to handle GET requests to the root of the website. You have a corresponding method in Application for all commonly used HTTP verbs (GET, POST, DELETE, HEAD, PUT, PATCH, CONNECT and OPTIONS). For a basic website, only GET and POST are likely to be used.
Pippo comes with a “pseudo” verb ANY that meant the route applied to any HTTP methods/verbs.

ANY("/example", new CSRFHandler());

The route that is defined first takes precedence over other matching routes. So the ordering of routes is crucial to the behavior of an application.

Each defined route has an urlPattern. The route can be static or dynamic:

  • static (“/”, “/hello”, “/contacts/1”)
  • dynamic
    • regex (“/.*”)
    • parameterized (“/contact/{id}”, “/contact/{id: [0-9]+}”)

As you can see, it’s easy to create routes with parameters. A parameter is wrapped by curly braces {name} and can optionally specify a regular expression.

You can retrieve the path parameter value for a request in type safe mode using:

GET("/contact/{id}", routeContext -> {
    // retrieve some parameters from request
    int id = routeContext.getParameter("id").toInt(0);
    String action = routeContext.getParameter("action").toString("new");

    // render a template using above parameters
    Map<String, Object> model = new HashMap<>();
    model.put("id", id);
    model.put("action", action)
    routeContext.render("contact", model);
});

If you want to be more rigorous you can use something like:

GET("/contact/{id: [0-9]+}", routeContext -> ... );

Pippo comes with some builtin route handlers that can be useful for you:

  • TemplateHandler (render a template)
  • RedirectHandler (redirect to another route/url)
  • TrailingSlashHandler (add or remove the trailing slash to/from path)
  • CSRFHandler (generates and validates a CSRF token)
  • UrlResourceHandler
  • ClasspathResourceHandler
  • PublicResourceHandler
  • WebjarsResourceHandler
  • MeteredHandler, TimedHandler, CountedHandler (metrics)

and the list can continue.

Now, we will talk a little bit about TrailingSlashHandler because is important and the problem that it solves is not so visible. If we add a route as in the below example

GET("/test", routeContext -> routeContext.send("Test"));

and we type localhost/test/ in browser we will see that the result is 404 (not found) http code. Of course that localhost/test works OK. Explanation for 404 on localhost/test/ is that we registered a route only for /test path. To obtain the same result for both urls (with and without \ character at end) we will use TrailingSlashHandler (as before filter)

// add trailing slash filter
ANY("/.*", new TrailingSlashHandler(false)); // remove trailing slash

// add test route
GET("/test", routeContext -> routeContext.send("Test"));

Now, if you type localhost/test or localhost/test/ in browser, the result is the same.

Named routes

Named routes make referring to routes when generating redirects or URLs more convenient. You may specify a name for a route like so:

GET("/blogs/{year}/{month}/{day}/{title}", routeContext -> routeContext.render("myTemplate")).named("blog");

Now, you may use the route’s name when generating URLs or redirects:

Map<String, Object> parameters = ...
routeContext.uriFor("blog", parameters);
// or
routeContext.redirect("blog", parameters);

Content-Type by Suffix

You may optionally specify the response content-type with a URI suffix by appending a special regex pattern to your Route declaration.

The examples below assume you have a ContentTypeEngine registered for JSON, XML, and YAML.

// Register a route that optionally respects a content-type suffix
// e.g. /contact/54
//      /contact/54.json
//      /contact/54.xml
//      /contact/54.yaml
GET("/contact/{id: [0-9]+}(\\.(json|xml|yaml))?", routeContext -> routeContext.send(contact));

// Register a route that requires a content-type suffix
// e.g. /contact/54.json
//      /contact/54.xml
//      /contact/54.yaml
GET("/contact/{id: [0-9]+}(\\.(json|xml|yaml))", routeContext -> routeContext.send(contact));

// Register a route that requires a content-type suffix
// e.g. /contact/john.json
//      /contact/john.xml
//      /contact/john.yaml
GET("/contact/{id}(\\.(json|xml|yaml))", routeContext -> routeContext.send(contact));

Note:

If you specify your parameter without a regex pattern, like the third example (e.g. {id}), the value of id will include your suffix unless you require the suffix using the pattern in the second example.

Route groups

RouteGroup allow you to prefix uriPattern, across a large number of routes without needing to define this attribute on each individual route.
Also you can add (route) filters for all routes of the group.

The RouteGroup is a concept very import when you are forced to deal with a considerable number of routes in your application.

The below snippet defines a UserRoutes group:

public class UserRoutes extends RouteGroup {

    public UserRoutes() {
        super("/user");

        // BEFORE FILTERS
        addBeforeFilters();

        // CRUD ROUTES
        GET("/{id}", routeContext -> ... ); // retrieve
        POST("/", routeContext -> ... ); // create
        PUT("/{id}", routeContext -> ... ); // update
        DELETE("/{id}", routeContext -> ... ); // delete
    }

}

Add the UserRoutes group to application:

public class PippoApplication extends Application {

    @Override
    protected void onInit() {
        addRouteGroup(new UserRoutes());
    }

}

You can access the routes defined in UserRoutes using something like http://localhost:8338/user/1 (retrieve the user with id equals with 1).

Pippo has support for NESTED GROUPS.
The idea is that you can create completely decoupled routes groups. Using nested routes groups you can create a nice modular application.

public class DemoApplication extends Application {

    @Override
    protected void onInit() {
        addRouteGroup(new AdminRoutes());
    }

}

public class AdminRoutes extends RouteGroup {

    public AdminRoutes() {
        super("/admin");

        // BEFORE FILTERS
        addBeforeFilters();

        // ROUTES
        addRoutes();

        // GROUPS
        addRouteGroup(new UserRoutes());
        addRouteGroup(new CustomerRoutes());
    }

}

In above snippet I added (in DemoApplication) AdminRoutes as route group which contains UserRoutes and CustomerRoutes as child routes groups.
I don’t need to know when I create a RouteGroup where the group will be mount. For example I can have a modular web application where each plugin comes with own independent routes (user management -> UserRoutes, customer management -> CustomerRoutes).

You can see a more complex example in Matilda (a real life application built with Pippo).