Routes
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).