A route describes the interface for making requests to your application. It combines an HTTP method and a path pattern.
A route has an associated handler, which does some job and produces some kind of output (HTTP response).
Jooby offers two programming models for writing routes:
- Script routes via lambdas, like Sinatra or expressjs
- MVC routes via annotations, like Jersey or Spring (covered later)
A script route definition looks like this:
{
get("/", () -> "hey jooby");
}while an MVC route definition looks like this:
import org.jooby.mvc.GET;
import org.jooby.mvc.Path;
@Path("/")
public class Controller {
@GET
public String salute() {
return "hey jooby";
}
}MVC routes are covered later in detail. For simplicity, all the examples use script routes, but keep in mind you can do the same with MVC routes.
A route was created to handle GET requests at the root of our application. Any other HTTP method can be handled the same way.
If you need a POST:
post("/", () -> "hey jooby");or listen to any HTTP method:
use("*", "/", (req, rsp) -> "hey jooby");It's also possible to name a route explicitly:
get("/", () -> "hey jooby")
.name("salute");The default route name is anonymous. Naming a route is useful for debugging purposes (if you have two or more routes mounted on the same path) and for dynamic and advanced routing.
Jooby offers several flavors for routes:
get("/", () -> "hey jooby");This handler usually produces a constant value. The returned value will be sent to the client.
get("/", req -> "hey " + req.param("name").value());This handler depends on some external attribute which is available via the Request object. The return value will be sent to the client.
get("/", (req, rsp) -> rsp.send("hey " + req.param("name").value());This handler depends on some external attribute which is available to the Request object and the response is explicitly sent via the response.send method.
get("/", (req, rsp, chain) -> {
// do something
chain.next(req, rsp);
});This is the most advanced handler. You have access to the Request, the Response and the Route.Chain objects.
From a handler like this, it's possible to end a response by calling the response.send method, abort the request by throwing an Err or allow the request to proceed to the next handler in the pipeline by calling chain.next(req, rsp).
get("/", () -> "hey jooby");
get("/help", () -> "hey jooby");
get("/mail/inbox", () -> "hey jooby");get("/user/:id", req -> "hey " + req.param("id").value());
// alternative syntax
get("/user/{id}", req -> "hey " + req.param("id").value());
// matches a path element with the prefix "uid"
get("/user/uid{id}", req -> "hey " + req.param("id").value());
// regex
get("/user/{id:\\d+}", req -> "hey " + req.param("id").intValue());Request parameters will be covered later. For now all you need to know is that you can access a path parameter using Request.param(String).
com/t?st.html - matches com/test.html but also com/tast.html and com/txst.html
com/*.html - matches all .html files in the com directory
com/**/test.html - matches all test.html files underneath the com path
** - matches any path at any level
* - matches anything except /
**:name or {name:**} - matches any path at any level and binds the match to the request parameter name
Static files are located inside the public directory.
├── public
├── assets
| ├── js
| | └── index.js
| ├── css
| | └── style.css
| └── images
| └── logo.png
└── welcome.htmlThe assets method let you expose all the content from a folder:
{
assets("/assets/**");
}The asset route handler resolves requests like:
GET /assets/js/index.js
GET /assets/css/style.css
It's possible to map a single static file to a path:
{
assets("/", "welcome.html");
}A GET / will display the static file welcome.html.
Here is another example with webjars:
{
assets("/assets/**", "/META-INF/resources/webjars/{0}");
}which would respond to the following requests:
GET /assets/jquery/2.1.3/jquery.js
GET /assets/bootstrap/3.3.4/css/bootstrap.css
By default the asset handler is able to read files from the public folder, which is a classpath folder.
It's possible to specify an external file system location as well:
{
assets("/static/**", Paths.get("/www"));
}
A request to /static/images/logo.png is translated to the /www/images/logo.png file.
The assets.etag and assets.lastModified are two boolean properties that control the ETag and Last-Modified headers. Both are enabled by default.
The assets.cache.maxAge controls the Cache-Control header. Allowed value includes: 60, 1h, 365d, etc. This property is off by default: -1.
The asset handler goes one step forward and adds support for serving files from a CDN out of the box.
All you have to do is to define a assets.cdn property:
assets.cdn = "http://d7471vfo50fqt.cloudfront.net"{
assets("/assets/**");
}A GET to /assets/js/index.js will be redirected to: http://d7471vfo50fqt.cloudfront.net/assets/js/index.js
All these assets features are controlled globally from .conf file or individually:
{
assets("/assets/**")
.etag(true)
.cdn("http://d7471vfo50fqt.cloudfront.net")
.maxAge("365d");
}There is also a powerful and all-round awesome assets module. This module can validate, concatenate, minify or compress JavaScript and CSS assets.
Routes are executed in the order they are defined. That means that the ordering of routes is crucial to the behavior of an application. Let's examine how this works with some examples:
get("/abc", req -> "first");
get("/abc", req -> "second");A call to /abc produces a response of first. If we revert the order:
get("/abc", req -> "second");
get("/abc", req -> "first");It produces a response of second.
As you can see ORDER IS VERY IMPORTANT.
How come it's legal with two or more routes on the same path?
Because this is how filters for routes are enabled.
A route handler accepts a third parameter, commonly named chain, which refers to the next route handler in line:
get("/abc", (req, rsp, chain) -> {
System.out.println("first");
chain.next(req, rsp);
});
get("/abc", (req, rsp) -> {
rsp.send("second");
});Again the order of route definition is important. Forgetting this will cause your application behave unpredictably. You will learn more about this behavior in the examples in the next section.
When a request matching a route definition is made to the server, the associated callback functions kick in to process the request and send a response. This is called a route pipe or stack.
Routes are like a plumbing pipe, with requests starting at the first route and then working their way "down" the route stack processing for each path they match.
Each route handler has the capability to send a response or pass the request on to the next route handler in the current stack.
Route handlers, also have access to the chain object, which happens to be the next callback function in the pipe. To make the chain object available to the callback function, pass it as a method parameter:
get("/", (req, rsp, chain) -> {
chain.next(req, rsp);
});If there is no matching callback function after the current callback function, next refers to the built-in 404 error handler, and will be triggered when you call it.
Try to guess the output of:
get("/", (req, rsp, chain) -> rsp.send("first"));
get("/", (req, rsp, chain) -> rsp.send("second"));
get("/", (req, rsp) -> rsp.send("third"));Will the server print all of them? "first"? "third"?
It prints "first". The act of doing a {{rsp_send}} will terminate the flow of the request then and there; the request is not passed on to any other route handler.
So, how can you specify multiple handlers for a route, and use them all at the same time? Call the {{chain_next}} function from the callback, without calling {{rsp_send}} (as that would terminate the request). Here is an example:
get("/", (req, rsp, chain) -> {
System.out.println("first");
chain.next(req, rsp);
});
get("/", (req, rsp, chain) -> {
System.out.println("second");
chain.next(req, rsp);
});
get("/", (req, rsp) -> {
rsp.send("third");
});Alternatively, if you always call {{chain_next}} just use the (req, rsp handler:
get("/", (req, rsp) -> {
System.out.println("first");
});
get("/", (req, rsp) -> {
System.out.println("second");
});
get("/", (req, rsp) -> {
rsp.send("third");
});The third argument is required if you need to decide whether the next route needs to be executed or not. If you always call {{chain_next}} the third argument isn't required and does exactly what the second argument handler does: it always calls {{chain_next}}.
A good example for a filter is to handle e.g. authentication:
get("/", (req, rsp, chain) -> {
if (condition) {
// It is OK
chain.next(req, rsp);
} else {
throw new Route.Err(403);
}
});A route can produce different results based on the Accept header:
get("/", () ->
Results
.when("text/html", () -> Results.html("viewname").put("model", model))
.when("application/json", () -> model)
.when("*", () -> Status.NOT_ACCEPTABLE)
);This will perform content-negotiation on the Accept HTTP header of the request object. It selects a handler for the request, based on the acceptable types ordered by their quality factor. If the header is not specified, the first callback is invoked. When no match is found, the server responds with 406 Not Acceptable, or invokes the default callback: **/*.