do more, more easily
- Simple and effective programming model for building small and large scale web applications
- Built with developer productivity in mind
- Plain Java DSL for routes (no xml, or similar)
- Reflection, annotations and dependency injection are kept to a minimum and in some cases is completely optional
- No classpath hell, the default deployment model uses a normal JVM bootstrap
- Modules are easy to use and do as little as possible
Jooby keeps it simple yet powerful.
application
A Jooby app looks like:
public class App extends Jooby { // 1.
{
// 2. add a route
get("/", () -> "Hello");
}
public static void main(String[] args) {
// 3. run my app
run(App::new, args);
}
}
1) Create a new App extending Jooby
2) Define your application in the instance initializer
3) Run the application.
life cycle
Application provides start and stop events. These events are useful for starting/stopping services.
onStart/onStop events
Start/stop callbacks are accessible via application:
{
onStart(() -> {
log.info("starting app");
});
onStop(() -> {
log.info("stopping app");
});
onStarted(() -> {
log.info("app started");
});
}
Or via module:
public class MyModule implements Jooby.Module {
public void configure(Env env, Config conf, Binder binder) {
env.onStart(() -> {
log.info("starting module");
});
env.onStop(() -> {
log.info("stopping module");
});
env.onStarted(() -> {
log.info("app started");
});
}
}
The onStart
callbacks are part of bootstrap and executed before the server is ready. The onStarted
callbacks are executed when the server is ready.
Modules are covered later all you need to know now is that you can start/stop module as you usually do from your application.
order
Callback order is preserved:
{
onStart(() -> {
log.info("first");
});
onStart(() -> {
log.info("second");
});
onStart(() -> {
log.info("third");
});
}
Order is useful for service dependencies, for example if ServiceB
should be started after ServiceA
.
service registry
You have access to the the service registry from start/stop events:
{
onStart(registry -> {
MyService service = registry.require(MyService.class);
service.start();
});
onStop(registry -> {
MyService service = registry.require(MyService.class);
service.stop();
});
}
PostConstruct/PreDestroy annotations
If you prefer annotations you can do:
@Singleton
public class MyService {
@PostConstruct
public void start() {
// ...
}
@PreDestroy
public void stop() {
// ...
}
}
App.java:
{
lifeCycle(MyService.class);
}
Service must be a Singleton object.
conf, env and logging
Jooby is configurable via Lightbend’s Config library.
By default, Jooby expects to find an application.conf
file at the root of the classpath. You can find the .conf
file under the conf
classpath directory.
getting properties
via script:
{
get("/", req -> {
Config conf = require(Config.class);
String myprop = conf.getString("myprop");
...
});
}
via @Named
annotation:
public class Controller {
@Inject
public Controller(@Named("myprop") String myprop) {
...
}
}
via Module:
public class MyModule implements Jooby.Module {
public void configure(Env env, Config conf, Binder binder) {
String myprop = conf.getString("myprop");
...
}
}
type conversion
Automatic type conversion is provided when a type:
1) Is a primitive, primitive wrapper or String
2) Is an enum
3) Has a public constructor that accepts a single String argument
4) Has a static method valueOf that accepts a single String argument
5) Has a static method fromString that accepts a single String argument. Like java.util.UUID
6) Has a static method forName that accepts a single String argument. Like java.nio.charset.Charset
You’re free to inject the entire com.typesafe.config.Config
object or sub-path/tree
of it.
environment
Jooby internals and the module system rely on the application.env
property. By default, this property is set to: dev
.
This special property is represented at runtime with the Env class.
For example: a module might decided to create a connection pool, cache, etc. when application.env
isn’t set to dev
.
The application.env
property can be set as command line argument as well:
Using a fat jar
:
java -jar myfat.jar prod
Using stork:
bin/myapp --start prod
turning features on or off
As described before, the application.env
property defines the environment where the application is being executed. It’s possible to turn on/off specific features based on the application environment:
{
on("dev", () -> {
use(new DevModule());
});
on("prod", () -> {
use(new ProdModule());
});
}
There is a ~
(complement) operator:
{
on("dev", () -> {
use(new DevModule());
}).orElse(() -> {
use(new ProdModule());
});
}
The environment callback
has access to the config
object, see:
{
on("dev", conf -> {
use(new DevModule(conf.getString("myprop")));
});
}
special properties
Here is the list of special properties available in Jooby:
- pid: application process ID.
- application.name: describes the name of your application. Default is:
single package name
of where you define your bootstrap class, for example forcom.foo.App
application’s name isfoo
. - application.version: application version. Default is:
getClass().getPackage().getImplementationVersion()
(automatically set by Maven). - application.class: fully qualified name of the bootstrap class.
- application.secret: If present, the session cookie will be signed with the
application.secret
. - application.tmpdir: location of the application temporary directory. Default is:
${java.io.tmpdir}/${application.name}
. - application.charset: charset to use. Default is:
UTF-8
. - application.lang: locale to use. Default is:
Locale.getDefault()
. - application.dateFormat: date format to use. Default is:
dd-MM-yyyy
. - application.numberFormat: number format to use. Default is:
DecimalFormat.getInstance("application.lang")
. - application.tz: time zone to use. Default is:
ZoneId.systemDefault().getId()
. - runtime.processors: number of available processors.
- runtime.processors-plus1: number of processors.
- runtime.processors-plus2: number of processors + 2.
- runtime.processors-x2: number of processors * 2.
precedence
Configuration files are loaded in the following order:
- system properties
- arguments properties
- (file://[application].[env].[conf])?
- (cp://[application].[env].[conf])?
- ([application].[conf])?
- [module].[conf]*
The first occurence of a property will take precedence.
system properties
System properties can override any other property. A system property is set at startup time, like:
java -Dapplication.env=prod -jar myapp.jar
arguments properties
Command line arguments has precedence over file system
or classpath
configuration files:
java -jar myapp.jar application.env=prod
Or using the shortcut for application.env
:
java -jar myapp.jar prod
Unqualified properties are bound to application
, so:
java -jar myapp.jar port=8888 path=/myapp
automatically translate to:
java -jar myapp.jar application.port=8888 application.path=/myapp
file://[application].[env].[conf]
A file system
configuration file has precedence over classpath
configuration file. Usefult to override a classpath
configuration file.
cp://[application].[env].[conf]
A classpath system conf
file has precedence over default conf
file.
application.conf
The default classpath conf
file: application.conf
[module].[conf]
A Module might have defined its own set of (default) properties via the Module.config method.
{
use(new Jdbc());
}
custom .conf
As mentioned earlier, the default conf
file is application.conf
, but you can use whatever name you prefer:
{
conf("myapp.conf");
}
example
.
└── conf
├── application.conf
├── application.uat.conf
├── application.prod.conf
├── logback.xml
├── logback.uat.xml
└── logback.prod.xml
{
// import Foo and Bar modules:
use(new Foo());
use(new Bar());
}
- Starting the application in
dev
produces aconf
tree similar to:
.
└── system properties
├── command line
├── application.conf
├── foo.conf
├── bar.conf
├── ...
└── ...
- Starting the application in
prod
produces aconf
tree similar to:
.
└── system properties
├── command line
├── application.prod.conf
├── application.conf
├── foo.conf
├── bar.conf
├── ...
└── ...
First-listed are higher priority.
For more details, please refer to the config documentation.
logging
Logging is done via logback. Logback bootstrap and configuration is described in detail here.
You will usually find the logback.xml
file inside the conf
directory. Also, you can define a logback.xml
file per application.env
by appending the env
. See some examples:
logback.uat.xml
whenapplication.env = uat
logback.prod.xml
whenapplication.env = prod
If the logback[.env].xml
file isn’t present, logback.xml
will be used as a fallback.
modules
Modules are a key concept for building reusable and configurable pieces of software.
Modules (unlike in other frameworks) are thin and do a lot of work to bootstrap and configure an external library, but they DO NOT provide a new level of abstraction nor [do] they provide a custom API to access functionality in that library. Instead they expose the library components as they are.
Modules like in Guice are used to wire services, connect data, etc…
There is an extensive module ecosystem which makes Jooby a full stack framework (when needed).
do less and be flexible
Do less might sounds confusing, but is the key to flexibility.
A module does as little as possible (a key difference with other frameworks). A module for a library X
should:
- Bootstrap X
- Configure X
- export the API of X
This means that a module should NOT create a wrapper for a library. Instead it should provide a way to extend, configure and use the external library.
This principle keeps modules small, maintainable and flexible.
creating modules
A module is represented by the Jooby.Module class. The configure callback looks like this:
public class M1 implements Jooby.Module {
public void configure(Env env, Config conf, Binder binder) {
binder.bind(...).to(...);
}
}
The configure callback is similar to a Guice module, except you can access to the Env and config objects.
properties
In addition to the configure callback, a module in Jooby has one additional method: Module.config. The config
method allows a module to set default properties.
public class M1 implements Jooby.Module {
public void configure(Env env, Config config, Binder binder) {
binder.bind(...).to(...);
}
public Config config() {
return ConfigFactory.parseResources(getClass(), "m1.properties");
}
}
usage
A module must be registered at startup time:
import org.jooby.Jooby;
public class MyApp extends Jooby {
{
// as lambda
use((env, config, binder) -> {
binder.bind(...).to(...);
});
// as instance
use(new M1());
use(new M2());
}
}
You can start or stop services from a module:
public class M1 implements Jooby.Module {
public void configure(Env env, Config config, Binder binder) {
env.onStart(() -> {
// Start services
});
env.onStop(() -> {
// Stop services
});
}
}
Or export routes:
public class M1 implements Jooby.Module {
public void configure(Env env, Config config, Binder binder) {
Router router = env.router();
router.get("/m1", () -> "I'm a module!");
}
}
routes
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)
creating routes
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.
route handler
Jooby offers several flavors for routes:
functional handler: ()
get("/", () -> "hey jooby");
This handler usually produces a constant value. The returned value will be sent to the client.
functional handler: req
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.
handler: (req, rsp)
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.
filter: (req, rsp, chain)
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).
path patterns
static patterns
get("/", () -> "hey jooby");
get("/help", () -> "hey jooby");
get("/mail/inbox", () -> "hey jooby");
var/regex patterns
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).
ant style patterns
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
Static files are located inside the public
directory.
├── public
├── assets
| ├── js
| | └── index.js
| ├── css
| | └── style.css
| └── images
| └── logo.png
└── welcome.html
The 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
file system location
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.
ETag, Last-Modified and Cache-Control
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
.
using a CDN
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");
}
assets module
There is also a powerful and all-round awesome assets module. This module can validate, concatenate, minify or compress JavaScript and CSS assets.
precedence and order
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.
request handling
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(req, rsp) 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(req, rsp) 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(req, rsp) the third argument isn’t required and does exactly what the second argument handler does: it always calls chain.next(req, rsp).
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);
}
});
content negotiation
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: **/*
.
interceptors
An interceptor allows to customize an HTTP request and response at three stages:
- before the actual handler is invoked
- after the handler was executed, but before we send the response
- when the response is complete and sent
before
Allows for customized handler execution chains. It will be invoked before the actual handler.
{
before((req, rsp) -> {
// your code goes here
});
}
You are allowed to modify the request and response objects. Please note that the before
handler is just syntactic sugar for filter. For example, the before
handler was implemented as:
{
use("*", "*", (req, rsp, chain) -> {
before(req, rsp);
chain.next(req, rsp);
});
}
A before
handler must be registered before the actual handler you want to intercept.
{
before("/path", (req, rsp) -> {
// your code goes here
});
get("/path", req -> {
// your code goes here
return ...;
});
}
If you reverse the order, it won’t work.
Remember: routes are executed in the order they are defined and the pipeline is executed as long as you don’t generate a response.
after
Allows for customization of the response before sending it. It will be invoked at the time a response need to be sent.
{
after((req, rsp, result) -> {
// your code goes here
return result;
});
}
You are allowed to modify the request, response and result object. The handler returns a result which can be the same or an entirely new result. Please note that the after
handler is just syntactical sugar for filter. For example, the after
handler was implemented as:
{
use("*", (req, rsp, chain) -> {
chain.next(req, new Response.Forwarding(rsp) {
public void send(Result result) {
rsp.send(after(req, rsp, result);
}
});
});
}
Since after
is implemented by wrapping the response object. An after
handler must be registered before the actual handler you want to intercept.
{
after((req, rsp, result) -> {
// your code goes here
return result;
});
get("/path", req -> {
return "hello";
});
}
If you reverse the order, it won’t work.
Remember: routes are executed in the order they are defined and the pipeline is executed as long you don’t generate a response.
complete
Allows for logging and cleaning up of a request. Invoked after the response is sent.
{
complete("*", (req, rsp, cause) -> {
// your code goes here
});
}
You are NOT allowed to modify the request and response objects. The cause
is an Optional
with a Throwable
useful to identify problems. A common use case for the complete
handler is to clean up the request object and to log responses. Please note that the complete
handler is just syntactical sugar for filter. For example, the complete
handler was implemented as:
{
use("*", "*", (req, rsp, chain) -> {
Optional<Throwable> err = Optional.empty();
try {
chain.next(req, rsp);
} catch (Throwable cause) {
err = Optional.of(cause);
throw cause;
} finally {
complete(req, rsp, err);
}
});
}
A complete
handler must to be registered before the actual handler you want to intercept.
{
complete("/path", (req, rsp, cause) -> {
// your code goes here
});
get("/path", req -> {
return "hello";
});
}
If you reverse the order, it won’t work.
Remember: routes are executed in the order they are defined and the pipeline is executed as long you don’t generate a response.
example
Suppose you have a transactional resource, like a database connection. The next example shows you how to implement a simple and effective transaction-per-request
pattern:
{
// start transaction
before("/api/*", (req, rsp) -> {
DataSource ds = require(DataSource.class);
Connection connection = ds.getConnection();
Transaction trx = connection.getTransaction();
trx.begin();
req.set("connection", connection);
return true;
});
// commit/rollback transaction
complete("/api/*", (req, rsp, cause) -> {
// unbind connection from request
try(Connection connection = req.unset("connection").get()) {
Transaction trx = connection.getTransaction();
if (cause.ifPresent()) {
trx.rollback();
} else {
trx.commit();
}
}
});
// your transactional routes goes here
get("/api/something", req -> {
Connection connection = req.get("connection");
// work with connection
});
}
mvc routes
Mvc routes are similar to controllers in Spring and resources in Jersey with some minor enhancements and simplifications.
@Path("/")
public class MyRoutes {
@GET
public Result home() {
return Results.html("home").put("model", model);
}
}
Annotations are identical to Jersey/JAX-RS and they can be found under the package org.jooby.mvc
.
NOTE: Jooby doesn’t implement the JAX-RS specification. That is why it has its own version of the annotations.
An mvc route can be injected by Guice:
@Path("/")
public class MyRoutes {
@Inject
public MyRoutes(DepA a, DepB) {
...
}
@GET
public Result home() {
return Results.html("home").put("model", model);
}
@GET
@Path("/search")
public List<SearchResult> search() {
List<SearchResult> result = ...;
return result;
}
@POST
@Path("/form")
public MyObject submit(MyObject form) {
...
return Results.html("success");
}
}
NOTE: MVC routes are NOT singleton, unless you explicitly annotated the route as a Singleton:
import javax.inject.Singleton;
@Singleton
@Path("/")
public class MyRoutes {
@Inject
public MyRoutes(DepA a, DepB) {
...
}
@GET
public Result home() {
return Results.html("home").put("model", model);
}
@GET
@Path("/search")
public List<SearchResult> search() {
List<SearchResult> result = ...;
return result;
}
@POST
@Path("/form")
public MyObject submit(MyObject form) {
...
return Results.html("success");
}
}
registering an mvc route
Mvc routes must be registered, there is no auto-discover feature, no classpath scanning, …, etc.
The order in which you define your routes is very important and it defines how your app will work.
This is one of the reason why mvc routes need to be explicitly registered.
The other reason is that declaring the route explicitly helps to reduce bootstrap time.
So, how do I register an mvc route?
In the same way everything else is registered in Jooby, from your application class:
public class App extends Jooby {
{
use(MyRoutes.class);
}
}
Again, handlers are registered in the order they are declared, so:
@Path("/routes")
public class MyRoutes {
@GET
public void first() {
log.info("first");
}
@GET
public void second() {
log.info("second");
}
@GET
public String third() {
return "third";
}
}
A call to /routes
will print: first, second and produces a response of third.
If you find the explicit registration odd or have too many MVC routes
, checkout the classpath scanner module which automatically find and register MVC routes
.
request parameters
A method parameter represents a HTTP parameter:
@GET
public List<Object> search(String q) {
return searcher.doSearch(q);
}
Here q can be any of the available parameter types and it will resolved as described in the request parameters section.
Optional parameters work in the same way, all you have to do is to declare them as java.util.Optional
:
@GET
public List<Object> search(Optional<String> q) {
return searcher.doSearch(q.orElse("*:*"));
}
Same for multi-value
parameters, just declare them as java.util.List
, java.util.Set
or java.util.SortedSet
:
@GET
public List<Object> search(List<String> q) {
return searcher.doSearch(q);
}
NOTE: The injected collection is immutable.
Same for file upload
@POST
public Object formPost(Upload file) {
...
}
Jooby uses the method parameter name and binds that name to a request parameter. If you want an explicit mapping or if the request parameter isn’t a valid Java identifier:
@GET
public List<Object> search(@Named("req-param") String reqParam) {
...
}
form submit
A form submitted as application/x-www-form-urlencoded
or multipart/form-data
doesn’t require anything:
@POST
public Result create(MyObject form) {
...
}
request body
Annotate the method parameter with the @Body annotation:
@POST
public MyObject create(@Body MyObject object) {
... do something with my object
}
request headers
Annotate the method parameter with the @Header annotation:
@GET
public List<Object> search(@Header String myHeader) {
...
}
Or, if the header name isn’t a valid Java identifier:
@GET
public List<Object> search(@Header("Last-Modified-Since") long lastModifedSince) {
...
}
response
A methods return type is sent to the client. Some examples:
@GET
public String sayHi(String name) {
// OK(200)
return "Hi " + name;
}
@GET
public Result dontSayGoodbye(String name) {
// NO_CONTENT(204)
return Results.noContent();
}
If you want to render a view, just return a view instance:
@GET
public Result home() {
return Results.html("home").put("model", model);
}
If you need to deal with HTTP metadata like: status code, headers, etc… use a [result] as the return type:
@GET
public Result handler() {
// 201 = created
return Results.with(model, 201);
}
dynamic / advanced routing
Dynamic routing allows you to filter a pipeline execution chain and produces a custom or even completely different response.
For example, suppose you need to serve different content based on the hostname:
{
use("*", (req, rsp, chain) -> {
if (req.hostname().equals("foo.com")) {
chain.next("/foo-branding", req, rsp);
} else {
chain.next("/bar-branding", req, rsp);
}
});
get("/", () -> Results.html("foo")).name("foo-branding");
get("/", () -> Results.html("bar")).name("bar-branding");
}
This application has two routes under root path: /
. Each of these route has a name
: foo-branding
and bar-branding
.
If you load the application from http://foo.com
the foo-branding
route will be executed, otherwise the bar-branding
route.
Dynamic routing is done over route.name(). From filter you provide a name filter
via chain.next(name, req, rsp) method.
Or group routes via with(Runnable):
{
with(() ->
get("/", () -> Results.html("foo"));
get("/api", () -> ...);
).name("foo-branding");
with(() ->
get("/", () -> Results.html("bar"));
get("/api", () -> ...);
).name("bar-branding");
}
Or group routes in their own application and then merge them into the main application:
public class FooBranding extends Jooby {
public FooBranding() {
super("foo-branding");
}
{
get("/", () -> Results.html("foo"));
...
}
}
public class BarBranding extends Jooby {
public BarBranding() {
super("bar-branding");
}
{
get("/", () -> Results.html("bar"));
...
}
}
/**
* Merge everything .
*/
public class App extends Jooby {
{
use("*", (req, rsp, chain) -> {
if (req.hostname().equals("foo.com")) {
chain.next("/foo-branding", req, rsp);
} else {
chain.next("/bar-branding", req, rsp);
}
});
use(new FooBranding());
use(new BarBranding());
}
}
Routes and routing in Jooby are so powerful!
properties
Routes have a few properties that let you extend basic functionality in one way or another including:
- attributes
- with, map and excludes operators
- consumes/produces types
attributes
Attributes let you annotate a route at application bootstrap time. It functions like static metadata available at runtime:
{
get("/path", ..)
.attr("foo", "bar");
}
An attribute consist of a name
and value
. Allowed values are primitives
, String
, enum
, class
or an array
of these types.
Attributes can be accessed at runtime in a request/response cycle. For example, a security module might check for a role
attribute, a sitemap generator might check for a priority
attribute, etc.
{
use((req, rsp, chain) -> {
User user = ...;
String role = req.route().attr("role");
if (user.hasRole(role)) {
chain.next(req, rsp);
}
throw new Err(403);
});
}
In MVC routes you can set attributes for all the web methods:
{
use(Controller.class)
.attr("foo", "bar");
}
Or via annotations
:
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public static @interface Role {
String value();
}
@Path("/path")
public class AdminResource {
@Role("admin")
public Object doSomething() {
...
}
}
{
use("*", (req, rsp) -> {
System.out.println(req.route().attributes())
});
}
The previous example will print: {role = admin}
.
Any runtime annotation is automatically added as route attributes following these rules:
- If the annotation has a
value
method, then we use the annotation’s name as the attribute name. - Otherwise, we use the method name as the attribute name.
request attributes vs route attributes:
Route attributes are created at bootstrap. They are global, and once set, they won’t change.
On the other hand, request attributes are created in a request/response cycle.
with operator
The with operator sets attributes, consumes/produces types, exclusions, etc. to one or more routes:
{
with(() -> {
get("/admin/1", ...);
get("/admin/2", ...);
}).attr("role", "admin");
}
map operator
The map operator converts a route output to something else:
{
// we got bar.. not foo
get("/foo", () -> "foo")
.map(value -> "bar");
// we got foo.. not bar
get("/bar", () -> "bar")
.map(value -> "foo");
}
If you want to apply a single map to several routes:
{
with(() -> {
get("/foo", () -> "foo");
get("/bar", () -> "bar");
}).map(v -> "foo or bar");
}
You can apply a Mapper to specific types:
{
with(() -> {
get("/str", () -> "str");
get("/int", () -> 1);
}).map(String v -> "{" + v + "}");
}
A call to /str
produces {str}
, while /int
just 1
.
NOTE: You can apply the map operator to routes that produces an output (a.k.a function routes).
For example, the map operator will be silently ignored here:
{
get("/", (req, rsp) -> {
rsp.send(...);
});
}
excludes
The excludes operator ignores what would otherwise have been a route path match:
{
use("*", (req, rsp) -> {
// all except /login
}).excludes("/login");
}
consumes
The consumes operator indicates the type of input the route can handle.
{
post("/", req -> {
MyObject json = req.body().to(MyObject.class);
}).consumes("json");
}
produces
The produces operator indicates the type of output the route can produce.
{
get("/", req -> {
return new MyObject();
}).produces("json");
}
request
The request object contains methods for reading parameters, headers and body (amongst others). In the next section, the most important methods of a request object will be explored. If you need more information, please refer to the javadoc.
parameters
Retrieval of a parameter is done via the: req.param(“name”) method.
The req.param(“name”) method always returns a mutant instance. A mutant has several utility methods for doing type conversion:
get("/", req -> {
int iparam = req.param("intparam").intValue();
String str = req.param("str").value();
String defstr = req.param("str").value("def");
// custom object type using type conversion
MyObject object = req.param("object").to(MyObject.class);
// file upload
Upload upload = req.file("file");
// multi value parameter
List<String> strList = req.param("strList").toList(String.class);
// custom object type using type conversion
List<MyObject> listObj = req.param("objList").toList(MyObject.class);
// custom object type using type conversion
Set<MyObject> setObj = req.param("objList").toSet(MyObject.class);
// optional parameter
Optional<String> optStr = req.param("optional").toOptional();
});
Multiple parameters can be retrieved at once:
GET /search?name=John&age=99&address[country]=AR&address[city]=BA
public class Profile {
String name;
int age;
Address address;
}
public class Address {
String country;
String city;
...
}
{
get("/search", req -> {
Profile profile = req.params(Profile.class);
System.out.println(profile.getName()); // print John
System.out.println(profile.getAge()); // print 99
System.out.println(profile.getAddress()); // print 99
});
}
Bean classes must have a default constructor
or a constructor annotated with javax.inject.Inject
.
parameter type and precedence
A request parameter can be present in:
1) path: /user/:id
2) query string: /user?id=...
3) form submit encoded as application/x-www-form-urlencoded
or multipart/form-data
(parameters take precedence in the order listed)
For example purposes, let’s consider a poorly constructed API where we have a route handler that accepts an id parameter in all three locations:
A call like:
curl -X POST -d "id=third" http://localhost:8080/user/first?id=second
Produces:
get("/user/:id", req -> {
// path param at idx = 0
assertEquals("first", req.param("id").value());
assertEquals("first", req.param("id").toList().get(0));
// query param at idx = 1
assertEquals("second", req.param("id").toList().get(1));
// form param at idx = 2
assertEquals("third", req.param("id").toList().get(2));
});
While clearly bad API design, this is a good example of how parameter precedence works.
parameter type conversion
Automatic type conversion is provided when a type:
- Is a primitive, primitive wrapper or String
- Is an enum
- Is an file upload
- Has a public constructor that accepts a single String argument
- Has a static method valueOf that accepts a single String argument
- Has a static method fromString that accepts a single String argument. Like
java.util.UUID
- Has a static method forName that accepts a single String argument. Like
java.nio.charset.Charset
- Is an Optional
, List , Set or SortedSet where T satisfies one of the previous rules
Custom type conversion is also possible:
parser((type, ctx) -> {
if (type.getRawType() == MyType.class) {
// convert the type here
return ctx.param(values -> new MyType(values.get(0)));
}
// no luck! move to next converter
return ctx.next();
});
get("/", req -> {
MyType myType = req.param("value").to(MyType.class);
});
See parser and renderer.
headers
Retrieval of request headers is done via: req.header(“name”). What goes for the request params applies for the headers as well.
body
Retrieval of the request body is done via req.body() or req.body(Class).
A parser is responsible for either parsing or converting the HTTP request body to something else.
There are a few built-in parsers for reading the body as String
, primitives
, …, etc.
Parsers are explained later. For now, all you need to know is that they can read or parse the HTTP body.
Script API:
{
post("/save", req -> {
MyObject object = req.body(MyObject.class);
...
});
}
MVC API:
public class Controller {
@POST
@Path("/save")
public MyObject save(@Body MyObject object) {
...
}
}
NOTE: Don’t use req.body(), req.body(Class) or @Body for a
POST
request encoded asapplication/x-www-form-urlencoded
ormultipart/form-data
, see the next section for such requests.
form submit
Form submit parsing is done via req.params(Class) or the req.form(Class) method:
public class Contact {
private int id;
private String name;
private String email;
public Contact(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
}
<form enctype="application/x-www-form-urlencoded" action="/save" method="post">
<input name="id" />
<input name="name" />
<input name="email" />
</form>
Script API:
{
post("/save", req -> {
Contact contact = req.params(Contact.class);
// save contact...
});
}
MVC API:
public class Controller {
@POST
@Path("/save")
public Result submit(Contact contact) {
...
}
}
Nested paths are supported via bracket: [name]
or dot: .name
notation:
public class Contact {
private int id;
private String name;
private String email;
// nested path
private Address address;
public Contact(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
}
public class Address {
private String line;
private String state;
private String country;
public Address(String line, String state, String country) {
this.line = line;
this.state = state;
this.country = country;
}
}
<form enctype="application/x-www-form-urlencoded" action="/save" method="post">
<input name="id" />
<input name="name" />
<input name="email" />
<input name="address[line]" />
<input name="address.state" />
<input name="address[country]" />
</form>
Tabular data is supported as well:
public class Contact {
private int id;
private String name;
private String email;
// nested path
private List<Address> address;
public Contact(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
}
<form enctype="application/x-www-form-urlencoded" action="/save" method="post">
<input name="id" />
<input name="name" />
<input name="email" />
<input name="address[0]line" />
<input name="address[0]state" />
<input name="address[0]country" />
<input name="address[1]line" />
<input name="address[1]state" />
<input name="address[1]country" />
</form>
NOTE: Constructor injection of nested or tabular objects isn’t supported. Nested/tabular object are injected via either method or field.
The injection rules are defined as follows (higher priority first):
- There is a only one constructor (nested/tabular data can’t be injected, just simple values).
- There are more than a single constructor but only one annotated with
javax.inject.Inject
(nested/tabular data can’t be injected, just simple values). - There is a setter like method that matches the parameter name, like
name(String)
,setName(String)
, etc… - There is a field that matches the parameter name
file upload
File uploads are accessible via the: request.file(name) method:
<form enctype="multipart/form-data" action="/upload" method="post">
<input name="myfile" type="file"/>
</form>
// Script API
{
post("/upload", req -> {
Upload upload = req.file("myfile");
...
upload.close();
});
}
// MVC API
class Controller {
@Path("/upload") @POST
public Object upload(Upload myfile) {
...
myfile.close();
}
}
You must close a file upload in order to release resources:
locals
Local attributes (a.k.a request attributes) are bound to the current request. They are created every time a new request is received and destroyed at the end of the request cycle.
{
use("*", (req, rsp) -> {
Object value = ...;
req.set("var", value);
});
get("/locals", req -> {
// optional local
Optional<String> ifValue = rsp.ifGet("var");
// required local
String value = rsp.get("var");
// local with default value
String defvalue = rsp.get("var", "defvalue");
// all locals
Map<String, Object> locals = req.attributes();
});
}
In mvc routes
request locals can be injected via the @Local
annotation by defining a method parameter with the same name as a request local:
@GET
public Result localAttr(@Local String var) {
// either var will be set to a previously configured value or an error is thrown when this method is requested
}
@GET
public Result ifLocalAttr(@Local Optional<String> var) {
// var will contain an optional that may be empty if no value was previously configured
}
@GET
public Result attributes(@Local Map<String, Object> attributes) {
// attributes contains the map of local attributes
}
Locals that are available by default include:
path
: the request path, i.e./myroute
contextPath
: application path (a.k.a context path). It is the value defined by:application.path
.- When the
Assets
module is enabled: your asset filesets postfixed with_css
and_js
- When the
Flash
module is enabled:flash
, the map of flash key/values
flash scope
The flash scope is designed to transport success and error messages between requests. It is similar to a Session but the lifecycle is shorter: data is kept for only one request.
The flash scope is implemented as a client side cookie, keeping the application stateless.
usage
import org.jooby.FlashScope;
...
{
use(new FlashScope());
get("/", req -> {
return req.ifFlash("success").orElse("Welcome!");
});
post("/", req -> {
req.flash("success", "The item has been created");
return Results.redirect("/");
});
}
The FlashScope is also available on mvc routes via the @Flash annotation:
@Path("/")
public class Controller {
// Access to the flashScope
@GET
public Object flashScope(@Flash Map<String, String> flash) {
...
}
// Access to a required flash attribute
@GET
public Object flashAttr(@Flash String foo) {
...
}
// Access to an optional flash attribute
@GET
public Object optionlFlashAttr(@Flash Optional<String> foo) {
...
}
}
Flash attributes are accessible from templates by prefixing the attribute’s name with flash.
. Here’s a (handlebars.java)[/doc/hbs] example:
{{#if flash.success}}
{{flash.success}}
{{else}}
Welcome!
{{/if}}
require
The request object has access to the application registry which give you access to application services.
Access to the registry is available via: request.require(type) methods:
get("/", req -> {
Foo foo = require(Foo.class);
return foo.bar();
});
Of course, the require
method doesn’t make sense on MVC routes
, because in that case you can inject dependencies:
@Path("/")
public class Controller {
private Foo foo;
@Inject
public Controller(Foo foo) {
this.foo = foo;
}
@GET
public String doSomething() {
return foo.bar();
}
}
access log
Log all matching incoming requests using the NCSA format (a.k.a common log format).
usage
{
use("*", new RequestLog());
...
}
Output looks like:
127.0.0.1 - - [04/Oct/2016:17:51:42 +0000] "GET / HTTP/1.1" 200 2
You probably want to configure the RequestLog
logger to save output into a new file:
<appender name="ACCESS" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>access.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>access.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<logger name="org.jooby.RequestLogger" additivity="false">
<appender-ref ref="ACCESS" />
</logger>
Since authentication is provided via a module or custom filter, there is no concept of a logged in or authenticated user. You can still log the current user by setting a user id provider at construction time:
{
use("*", (req, rsp) -> {
// authenticate user and set local attribute
String userId = ...;
req.set("userId", userId);
});
use("*", new RequestLogger(req -> {
return req.get("userId");
}));
}
In this example, an application filter sets a userId
request attribute and then that userId
is provided to the {@link RequestLogger}.
custom log function
By default all logging uses logback which is why the org.jooby.RequestLogger
was configured in logback.xml
.
If you want to log somewhere else, or want to use a different logging implementation:
{
use("*", new ResponseLogger()
.log(line -> {
System.out.println(line);
}));
}
Rather than printing the NCSA
line to stdout, it can of course be written to a database, a JMS queue, etc.
latency
{
use("*", new RequestLogger()
.latency());
}
This will append an entry at the end of the NCSA
output representing the number of ms
it took to process the request.
extended
{
use("*", new RequestLogger()
.extended());
}
Extend the NCSA
by adding the Referer
and User-Agent
headers to the output.
dateFormatter
{
use("*", new RequestLogger()
.dateFormatter(ts -> ...));
// OR
use("*", new RequestLogger()
.dateFormatter(DateTimeFormatter...));
}
Override the default formatter for the request arrival time defined by: Request#timestamp(). You can provide a function or an instance of DateTimeFormatter
.
The default formatter uses the default server time zone, provided by ZoneId#systemDefault()
. It’s possible to simply override the time zone as well:
{
use("*", new RequestLogger()
.dateFormatter(ZoneId.of("UTC"));
}
response
The response object contains methods for reading and setting headers, status code and body (amongst other things). In the next section you will encounter the most important methods of the response object. If you need more information please refer to the javadoc.
send
The rsp.send method is responsible for sending and writing data into the HTTP response.
A renderer is responsible for converting a Java Object into something else (json, html, etc..).
Let’s consider a simple example:
get("/", (req, rsp) -> rsp.send("hey jooby"));
get("/", req -> "hey jooby"); // or just return a value and Jooby will call send for you.
The send method will ask the Renderer API to format an object and write a response.
The resulting Content-Type
when not set is text/html
.
The resulting Status Code
when not set is 200
.
Some examples:
get("/", req -> {
// text/html with 200
String data = ...;
return data;
});
get("/", (req, rsp) -> {
// text/plain with 200 explicitly
String data = ...;
rsp.status(200)
.type("text/plain")
.send(data);
});
Alternative:
get("/", req -> {
// text/plain with 200 explicitly
String data = ...;
return Results.with(data, 200)
.type("text/plain");
});
headers
Retrieval of response headers is done via rsp.header(“name”). This method returns a Mutant which can be converted to any of the supported types.
Setting a header is pretty straightforward as well:
rsp.header("Header-Name", value).header("Header2", value);
send file
The send file API is available through the rsp.download* methods:
{
get("/download", (req, rsp) -> {
rsp.download("myfile", new File(...));
});
}
The download
method sets the following headers:
-
Content-Disposition
-
Content-Length
-
Content-Type
In the next example we explicitly set some of these:
{
get("/download", (req, rsp) -> {
rsp
.type("text/plain")
.header("Content-Disposition", "attachment; filename=myfile.txt;")
.download(new File(...));
});
}
session
Sessions are created on demand via: req.ifSession() or req.session().
Sessions have a lot of uses cases but the most commons are: authentication, storing information about current user, etc.
A session attribute must be a String
or a primitive. The session doesn’t allow storing of arbitrary objects. It’s intended as a simple mechanism to store basic data.
usage
{
get("/", req -> {
Session session = req.session();
// set attribute
session.set("foo", "bar");
// get attribute
return session.get("foo").value();
});
}
The previous example will use an in-memory
session. The next example uses a cookie:
{
cookieSession();
get("/", req -> {
Session session = req.session();
// set attribute
session.set("foo", "bar");
// get attribute
return session.get("foo").value();
});
}
The cookie session store depends on the application.secret
property. The cookie will be signed with the value of this property.
As an alternative to the memory
or the cookie
stores, you can choose any one of the high performance session stores provided by Jooby. There are provided session stores for redis
, memcached
, mongodb
, cassandra
, couchbase
, hazelcast
and a lot more.
{
cookieSession();
get("/", req -> {
Session session = req.session();
// set attribute
session.set("foo", "bar");
// get attribute
return session.get("foo").value();
});
}
no timeout
There is no timeout for sessions from the perspective of the server. By default, a session will expire when the user close the browser (a.k.a session cookie) or the cookie session has expired via the maxAge
attribute.
Session store implementations might or might not implement a server timeout
.
cookie
max-age
The session.cookie.maxAge
sets the maximum age in seconds. A positive value indicates that the cookie will expire after that many seconds have passed. Note that the value is the maximum age when the cookie will expire, not the cookie’s current age.
A negative value means that the cookie is not stored persistently and will be deleted when the browser exits.
Default maxAge is: -1
.
signed cookie
If the application.secret
property has been set, the session cookie will be signed with it.
cookie name
The session.cookie.name
indicates the name of the cookie that hold the session ID, by default: jooby.sid
. The cookie’s name can be explicitly set with cookie.name(“name”) on Session.Definition#cookie().
error handling
An error handler in Jooby is represented by the Err.Handler class and allows you to log and render exceptions.
NOTE: All headers are reset while generating the error response.
default err handler
The default error handler does content negotiation and optionally displays friendly error pages using a naming convention.
{
use(new TemplateEngine()); // Hbs, Ftl, etc...
use(new Json()); // A json renderer
get("/", () -> {
...
throw new IllegalArgumentException();
...
});
}
html
If a request to /
has an Accept: text/html
header. Then, the default Err handler will ask to a View.Engine to render the err
view.
The default model has these attributes:
- message: exception string
- stacktrace: exception stack-trace as an array of string
- status: status code, like
400
- reason: status code reason, like
BAD REQUEST
Here is a simple public/err.html
error page:
<html>
<body>
{{ "{{status" }}}}:{{ "{{reason" }}}}
</body>
</html>
The HTTP status code of the response will be set as well.
no html
If a request to /
has an Accept: application/json
header, the default Err handler will use a renderer to render the err
model.
{ "message": "...", "stacktrace": [], "status": 500, "reason": "..." }
In both cases, the error model is the result of err.toMap()
which creates a lightweight version of the exception.
The HTTP status code of the response will be set as well.
custom err handler
If the default view resolution and/or err model isn’t enough, you can create your own Err handler:
{
err((req, rsp, err) -> {
log.err("err found: ", err);
// do what ever you want here
rsp.send(...);
});
}
The Err handlers are executed in the order they were provided (like routes, parsers and renderers). The first Err handler that send an output wins!
catch a specific exception or status code
{
err(MyException1.class, (req, rsp, err) -> {
MyException1 cause = (MyException1) err.getCause();
// handle MyException1
});
err(MyException2.class, (req, rsp, err) -> {
MyException2 cause = (MyException2) err.getCause();
// handle MyException2
});
err((req, rsp, err) -> {
// handle any other exception
});
}
Or you can catch exception base on their response status code (see next section):
{
err(404, (req, rsp, err) -> {
// handle 404
});
err(503, (req, rsp, err) -> {
// handle 503
});
err((req, rsp, err) -> {
// handle any other exception
});
}
status code
The default status code for errors is 500
, except for:
| Exception | Status Code |
| ---------------------------------- | ----------- |
| java.lang.IllegalArgumentException | 400 |
| | |
| java.util.NoSuchElementException | 400 |
| | |
| java.io.FileNotFoundException | 404 |
custom status codes
Just throw an Err:
throw new Err(403);
or add a new entry in the application.conf
file:
err.com.security.Forbidden = 403
When you now throw a com.security.Forbidden
exception, the status code will be 403
.
parser and renderer
parser
A Parser is responsible for parsing the HTTP parameters or body to something else.
Automatic type conversion is provided when a type:
- Is a primitive, primitive wrapper or String
- Is an enum
- Is an Upload
- Has a public constructor that accepts a single String argument
- Has a static method valueOf that accepts a single String argument
- Has a static method fromString that accepts a single String argument. Like
java.util.UUID
- Has a static method forName that accepts a single String argument. Like
java.nio.charset.Charset
- Is an Optional
, List , Set or SortedSet where T satisfies one of previous rules
custom parser
Suppose you want to write a custom parser to convert a value into an integer
. In practice you won’t need a parser like that since it’s already provided, this is just an example.
Let’s see how to create our custom HTTP param parser:
parser((type, ctx) -> {
// 1
if (type.getRawType() == int.class) {
// 2
return ctx.param(values -> Integer.parseInt(values.get(0));
}
// 3
return ctx.next();
});
get("/", req -> {
int intValue = req.param("v").intValue();
...
});
Let’s have a closer look:
1) Check if current type matches our target type
2) Add a param callback
3) Can’t deal with current type, ask the next parser to resolve it
Now, when asking for the HTTP body:
get("/", req -> {
int intValue = req.body().intValue();
...
});
The custom parser won’t be able to parse the HTTP body, since it only works on HTTP parameters. In order to extend the custom parser and use it for the HTTP body:
parser((type, ctx) -> {
// 1
if (type.getRawType() == int.class) {
// 2
return ctx.param(values -> Integer.parseInt(values.get(0))
.body(body -> Integer.parseInt(body.text()));
}
// 3
return ctx.next();
});
Now it’s possible to ask for a HTTP param and/or body:
get("/", req -> {
int intValue = req.param("v").intValue();
...
});
post("/", req -> {
int intValue = req.body().intValue();
...
});
The parser API is very powerful. It lets you apply a parser to a HTTP parameter, set of parameters (like a form post), file uploads or a body. You are also free to choose if your parser applies for a Java Type and/or a Media Type, like the Content-Type
header.
For example a generic JSON parser looks like:
parser((type, ctx) -> {
if (ctx.type().name().equals("application/json")) {
return ctx.body(body -> fromJSON(body.text()));
}
return ctx.next();
});
Parsers are executed in the order they are defined.
If a parameter parser isn’t able to resolve a parameter a BAD REQUEST(400)
error will be generated.
If a body parser isn’t able to resolve a parameter an UNSUPPORTED_MEDIA_TYPE(415)
error will be generated.
renderer
A Renderer converts a Java Object to a series of bytes or text and writes them to the HTTP response.
There are a few built-in renderers:
- stream: copy an inputstream to the HTTP response and set a default type of:
application/octet-stream
- bytes: copy bytes to the HTTP response and set a default type of:
application/octet-stream
- byteBuffer: copy bytes to the HTTP response and set a default type of:
application/octet-stream
- readable: copy a readable object to the HTTP response and a default type of:
text/html
- text: copy the toString() result to the HTTP response and set a default type of:
text/html
custom renderer
Suppose you want to apply custom rendering for MyObject
. The renderer is as simple as:
render((value, ctx) -> {
if (value instanceOf MyObject) {
ctx.text(value.toString());
}
});
get("/", req -> {
return new MyObject();
});
A generic JSON renderer will look like:
render((value, ctx) -> {
if (ctx.accepts("json")) {
ctx
.type("json")
.text(toJson(value));
}
});
get("/", req -> {
return new MyObject();
});
The renderer API is simple and powerful. Renderers are executed in the order they were defined. The renderer which writes the response first wins!
view engine
A view engine is a specialized renderer that ONLY accepts instances of a view.
A view carries the template name + model data:
{
use(new MyTemplateEngine());
get("/", req -> Results.html("viewname").put("model", model);
}
In order to support multiple view engines, a view engine is allowed to throw a java.io.FileNotFoundException
when it’s unable to resolve a template. This passes control to the next view resolver giving it a chance to load the template.
There’s not much more to say about views and template engines, further details and documentation is provided in the specific module.
thread model
You can see Jooby as an event loop server
thanks to the supported web servers: Netty, Jetty and Undertow. The default web server is Netty.
Jooby isn’t a traditional thread server
where a HTTP request is bound to a thread.
In Jooby all the HTTP IO operations are performed in async & non blocking fashion. HTTP IO operations run in an IO thread (a.k.a event loop) while the application logic (your code) always run in a worker thread.
worker threads
The worker thread pool is provided by one of the supported web servers: Netty, Jetty or Undertow. To simplify application programming you can block a worker thread, for example you can safely run a jdbc query in a worker thread:
{
get("/search-db", () -> {
try(Connection db = require(Connection.class)) {
try(Statement stt = db.createStatement()) {
...
}
}
});
}
The web server can accept as many connections it can (as its on non blocking) while the worker thread might block.
The default worker thread pool is 20/100
. The optimal size depends on the business and work load your application is supposed to handle. It’s recommended to start with the default setup and then tune the size of the thread pool if the need arises.
Jooby favors simplicity over complexity hence allowing your code to block. However, it also provides you with more advanced options that will allow you to build async and reactive applications.
deferred
Async processing is achieved via: deferred result, with a deferred result an application can produces a result from a thread of its choice:
Script API:
{
get("/async", deferred(() -> {
return ...;
});
}
MVC API:
@GET
@Path("/async")
public Deferred async() {
return Deferred.deferred(() -> {
return ...;
});
}
The previous examples are just syntactic sugar
for:
return new Deferred(deferred -> {
try {
deferred.resolve(...);
} catch (Throwable x) {
deferred.reject(x);
}
});
You can get more syntactic sugar
if you add the AsyncMapper to your application:
Script API:
{
map(new AsyncMapper());
get("/async", () -> {
Callable<String> callable = () -> {
return ...;
};
return callable;
});
}
MVC API:
@GET
@Path("/async")
public Callable<String> async() {
return () -> {
return ...;
};
}
The AsyncMapper converts java.util.concurrent.Callable
and java.util.concurrent.CompletableFuture
objects to deferred objects.
Another important thing to notice is that the deferred will run in the caller thread (i.e. worker thread), so by default there is no context switch involved in obtaining a deferred result:
{
get("/async", () -> {
String callerThread = Thread.current().getName();
return Deferred.deferred(() -> {
assertEquals(callerThread, Thread.current().getName());
return ...;
});
});
}
This might not seem optimal at first, but there are some benefits to this:
-
It makes it very easy to set up a default executor (this will be explained shortly)
-
It provides better integration with async & reactive libraries. A
direct
executor avoids the need of switching to a new thread and then probably dispatch (again) to a different thread provided by a library.
executor
As previously mentioned, the default executor runs in the caller thread (a.k.a direct executor). Let’s see how to override the default executor:
{
executor(new ForkJoinPool());
get("/async", deferred(() -> {
return ...;
});
}
Done! Now all our deferred results run in a ForkJoinPool
. It’s also possible to specify an alternative executor:
Script API:
{
executor(new ForkJoinPool());
executor("jdbc", Executors.newFixedThreadPool(10));
get("/", deferred(() -> {
return ...;
});
get("/db", deferred("jdbc", () -> {
return ...;
});
}
MVC API:
import static org.jooby.Deferred.deferred;
...
@GET
@Path("/")
public Deferred home() {
return deferred(() -> {
return ...;
});
}
@GET
@Path("/db")
public Deferred db() {
return deferred("jdbc", () -> {
return ...;
});
}
It’s worth mentioning that the executor(ExecutorService) methods automatically shutdown
at application shutdown time.
promise
The deferred contains two useful methods:
These two methods allow you to use a deferred object as a promise
:
Script API:
{
get("/", promise(deferred -> {
try {
deferred.resolve(...);
} catch (Throwable x) {
deferred.reject(x);
}
});
}
MVC API:
@Path("/")
@GET
public Deferred promise() {
return new Deferred(deferred -> {
try {
deferred.resolve(...);
} catch (Throwable x) {
deferred.reject(x);
}
});
}
The “promise” version of the deferred object is a key concept for integrating with external libraries.
advanced configuration
Suppose you want to build a truly async application and after a deep analysis of your business demands you realize your application needs to:
- Access a database
- Call a remote service
- Make a CPU intensive computation
These are the 3 points where your application is supposed to block and wait for a result.
Let’s start by reducing the worker thread pool to the number of available processors:
server.threads.Min = ${runtime.processors}
server.threads.Max = ${runtime.processors}
With this change, you need to be careful to avoid any blocking code on routes, otherwise performance will suffer.
Let’s create a custom thread pool for each blocking access:
{
executor("db", Executors.newCachedThreadPool());
executor("remote", Executors.newFixedThreadPool(32));
executor("intensive", Executors.newSingleThreadExecutor());
}
For database
access, we use a cached
executor which will grow without a limit but free and release threads that are idle after 60s
.
For remote
service, we use a fixed
executor of 32
threads. The number 32
is just a random number for the purpose of the example.
For intensive
computations, we use a single
thread executor. Computation is too expensive and we want one and only one running at any time.
{
executor("db", Executors.newCachedThreadPool());
executor("remote", Executors.newFixedThreadPool(32));
executor("intensive", Executors.newSingleThreadExecutor());
get("/nonblocking", () -> "I'm nonblocking");
get("/list", deferred("db", () -> {
Database db = require(Database.class);
return db.fetch();
});
get("/remote", deferred("remote", () -> {
RemoteService rs = require(RemoteService.class);
return rs.call();
});
get("/compute", deferred("intensive", () -> {
return someCPUIntensiveTask();
});
}
Here’s the same example with rx java:
{
get("/nonblocking", () -> "I'm nonblocking");
get("/list", deferred(() -> {
Database db = require(Database.class);
Observable.<List<String>> create(s -> {
s.onNext(db.fetch());
s.onCompleted();
}).subscribeOn(Schedulers.io())
.subscribe(deferred::resolve, deferred::reject);
}));
});
get("/remote", deferred(() -> {
RemoteService rs = require(RemoteService.class);
Observable.<List<String>> create(s -> {
s.onNext(rs.call());
s.onCompleted();
}).subscribeOn(Schedulers.io())
.subscribe(deferred::resolve, deferred::reject);
}));
});
get("/compute", deferred(() -> {
Observable.<List<String>> create(s -> {
s.onNext(someCPUIntensiveTask());
s.onCompleted();
}).subscribeOn(Schedulers.computation())
.subscribe(deferred::resolve, deferred::reject);
}));
});
}
The main differences are:
- the default executor is kept
direct
. No new thread is created and context switching is avoided. - the deferred object is used as a
promise
and integrate with rx java. - different thread pool semantics is achieved with the help of rx schedulers.
This is just another example to demonstrate the value of the deferred object, since a rxjava module is provided which takes care of binding the deferred object into Observables
.
That sums up everything about the deferred object. It allows you to build async and reactive applications and at the same time: keep it simple (a Jooby design goal).
You’re also invited to check out the available async/reactive modules.
tests
This section will show you how to run unit and integration tests with Jooby.
unit tests
There are two available programming models:
- the script programming model; and
- the mvc programming model
Testing MVC
routes is pretty straightforward since a route is bound to a method of some class. This makes it simple to mock and run unit tests against these kinds of routes.
To test script
routes is more involved since a route is represented by a lambda
and there is no easy or simple way to get access to the lambda object.
For this reason there’s a MockRouter provided to simplify unit testing of script routes
:
usage
public class MyApp extends Jooby {
{
get("/test", () -> "Hello unit tests!");
}
}
A unit test for this route, looks like:
@Test
public void simpleTest() {
String result = new MockRouter(new MyApp())
.get("/test");
assertEquals("Hello unit tests!", result);
}
Just create a new instance of MockRouter with your application and call one of the HTTP method, like get
, post
, etc…
mocks
You’re free to choose the mock library of your choice. Here is an example using EasyMock:
{
get("/mock", req -> {
return req.path();
});
}
A test with EasyMock looks like:
@Test
public void shouldGetRequestPath() {
Request req = EasyMock.createMock(Request.class);
expect(req.path()).andReturn("/mypath");
EasyMock.replay(req);
String result = new MockRouter(new MyApp(), req)
.get("/mock");
assertEquals("/mypath", result);
EasyMock.verify(req);
}
You can mock a Response object similarly:
{
get("/mock", (req, rsp) -> {
rsp.send("OK");
});
}
A test with EasyMock looks like:
@Test
public void shouldUseResponseSend() {
Request req = EasyMock.createMock(Request.class);
Response rsp = EasyMock.createMock(Response.class);
rsp.send("OK");
EasyMock.replay(req, rsp);
String result = new MockRouter(new MyApp(), req, rsp)
.get("/mock");
assertEquals("OK", result);
EasyMock.verify(req, rsp);
}
What about external dependencies? This is handled similarly:
{
get("/", () -> {
HelloService service = require(HelloService.class);
return service.salute();
});
}
@Test
public void shouldMockExternalDependencies() {
HelloService service = EasyMock.createMock(HelloService.class);
expect(service.salute()).andReturn("Hola!");
EasyMock.replay(service);
String result = new MockRouter(new MyApp())
.set(service)
.get("/");
assertEquals("Hola!", result);
EasyMock.verify(service);
}
The MockRouter#set(Object) calls push and will register an external dependency (usually a mock). This makes it possible to resolve services from require
calls.
deferred
Mocking of promises is possible as well:
{
get("/", promise(deferred -> {
deferred.resolve("OK");
}));
}
@Test
public void shouldMockPromises() {
String result = new MockRouter(new MyApp())
.get("/");
assertEquals("OK", result);
}
The previous test works for deferred routes:
{
get("/", deferred(() -> {
return "OK";
}));
}
integration tests
Integration tests are possible thanks to a JUnit Rule.
You can choose between @ClassRule
or @Rule
. The following example uses ClassRule
:
import org.jooby.test.JoobyRule;
public class MyIntegrationTest {
@ClassRule
private static JoobyRule bootstrap = new JoobyRule(new MyApp());
}
Here one and only one instance will be created, which means the application will start before the first test and stop after the last test. Application state is shared between tests.
With Rule
on the other hand, a new application is created per test. If you have N tests, the application will start and stop N times:
import org.jooby.test.JoobyRule;
public class MyIntegrationTest {
@Rule
private JoobyRule bootstrap = new JoobyRule(new MyApp());
}
Again you are free to select the HTTP client of your choice, like Fluent Apache HTTP client, REST Assured, etc.
Here is a full example with REST Assured:
import org.jooby.Jooby;
public class MyApp extends Jooby {
{
get("/", () -> "I'm real");
}
}
import org.jooby.test.JoobyRyle;
public class MyIntegrationTest {
@ClassRule
static JoobyRule bootstrap = new JoobyRule(new MyApp());
@Test
public void integrationTestJustWorks() {
get("/")
.then()
.assertThat()
.body(equalTo("I'm real"));
}
}
That’s all for now, enjoy testing!
https
HTTPS
is supported across the available server implementations. To enable HTTPS
, you need to set:
application.securePort = 8443
Or programmatically like:
{
securePort(8443);
}
certificates
Jooby comes with a self-signed certificate, useful for development and test. But of course, you should NEVER use it in the real world.
In order to setup HTTPS with a real certificate, you need to set these properties:
ssl.keystore.cert
: An X.509 certificate chain file in PEM format. It can be an absolute path or a classpath resource.ssl.keystore.key
: A PKCS#8 private key file in PEM format. It can be an absolute path or a classpath resource.
Optionally, you can set these properties too:
ssl.keystore.password
: Password of the keystore.key (if any). Default is: null/empty.ssl.trust.cert
: Trusted certificates for verifying the remote endpoint’s certificate. The file should contain an X.509 certificate chain in PEM format. Default uses the system default.ssl.session.cacheSize
: Set the size of the cache used for storing SSL session objects. 0 to use the default value.ssl.session.timeout
: Timeout for the cached SSL session objects, in seconds. 0 to use the default value.
As you can see, setup is very simple. All you need is your .crt
and .key
files.
At startup you will see a line similar to this one:
https://localhost:8443
http/2
HTTP/2.0
is supported across the available server implementations. To enable HTTP/2.0
you need to set these properties:
server.http2.enabled = true
application.securePort = 8443
Or programmatically like:
{
// HTTP/2.0 clear text (a.k.a h2c)
http2();
// HTTP/2.0 over TLS
securePort(8443);
get("/", req -> req.protocol());
}
Browsers don’t support H2C
(H2 clear text) which is why you need to configure HTTPS as well.
At bootstrap time
you will see a +h2
flag that indicates that HTTP/2.0
is enabled:
http://localhost:8080/ +h2
https://localhost:8443/ +h2
Some servers require special configuration (besides HTTPS). So it’s always good practice to check the HTTP/2.0
section of the server implementation to learn more about current support, limitations and/or configuration steps.
server push
Server push is supported via Request.push:
{
// HTTP/2.0 clear text (a.k.a h2c)
http2();
// HTTP/2.0 over TLS
securePort(8443);
assets("/js/**");
get("/", req -> {
req.push("/js/home.js");
return Results.html("home");
});
}
When the browser requests the home
page, we will send the /js/home.js
resource as well!
That’s all, but keep in mind that each server implementation might require some extra configuration steps to work, please check out the server documentation for more details.
web sockets
usage
{
ws("/", ws -> {
ws.onMessage(message -> System.out.println(message.value()));
ws.send("connected");
});
}
A WebSocket consists of a path pattern and a handler.
A path pattern can be as simple or complex as you need. All the path patterns supported by routes are supported.
The onOpen listener is executed on new connections, from there it’s possible to listen for messages and errors or to send data to the client.
Keep in mind that a WebSocket is different from a route. There is no stack/pipe or chain.
You can mount a socket to a path used by a route, but you can’t have two or more WebSockets under the same path.
send and broadcast
As seen earlier, a web socket can send data to the client via the ws.send(…) method.
The ws.broadcast(…) method does the same thing, but will send to all connected clients.
require
Access to existing services is provided through the ws.require(type) method:
ws("/", ws -> {
A a = ws.require(A.class);
});
listener
As with routes you can choose between two flavors for WebSocket
listeners:
script:
{
ws("/ws", ws -> {
MyDatabase db = ws.require(MyDatabase.class);
ws.onMessage(msg -> {
String value = msg.value();
database.save(value);
ws.send("Got: " + value);
});
});
}
class:
{
ws(MyHandler.class);
}
@Path("/ws")
class MyHandler implements WebSocket.OnMessage<String> {
WebSocket ws;
MyDatabase db;
@Inject
public MyHandle(WebSocket ws, MyDatabase db) {
this.ws = ws;
this.db = db;
}
@Override
public void onMessage(String message) {
database.save(value);
ws.send("Got: " + message);
}
}
Optionally, your listener could implement onOpen, onClose or onError. If you need all of them, use the handler interface.
consumes
A WebSocket can define a type to consume:
{
ws("/", ws -> {
ws.onMessage(message -> {
MyObject object = message.to(MyObject.class);
});
ws.send("connected");
})
.consumes("json");
}
Or via annotation for class listeners:
@Path("/ws")
@Consumes("json")
class MyHandler implements WebSocket.OnMessage<MyObject> {
public void onMessage(MyObject object) {
...
}
}
This is just a utility method for parsing a socket message into a Java Object. Consumes in WebSockets has nothing to do with content negotiation. Content negotiation is a route concept, it doesn’t apply for WebSockets.
produces
A WebSocket can define a type to produce:
{
ws("/", ws -> {
MyResponseObject object = ..;
ws.send(object);
})
.produces("json");
}
Or via annotation for class listeners:
@Path("/ws")
@Consumes("json")
@Produces("json")
class MyHandler implements WebSocket.OnMessage<MyObject> {
public void onMessage(MyObject object) {
ws.send(new MyResponseObject());
}
}
This is just a utility method for formatting Java Objects as text messages. Produces in WebSockets has nothing to do with content negotiation. Content negotiation is a route concept, it doesn’t apply for WebSockets.
server-sent events
Server-Sent Events (SSE) is a mechanism that allows the server to push data to the client once the client-server connection is established. After the connection has been established by the client, the server can send to the client whenever a new chunk of data is available. In contrast with websockets, SSE can only be used to send from the server to the client and not the other way round.
usage
{
sse("/path", sse -> {
// 1. connected
sse.send("data"); // 2. send/push data
});
}
Simple, effective and easy to use. The callback will be executed when a new client is connected. Inside the callback you can send data, listen for connection close events, etc.
The factory method sse.event(Object) will let you set event attributes:
{
sse("/path", sse -> {
sse.event("data")
.id("id")
.name("myevent")
.retry(5000L)
.send();
});
}
structured data
Other than raw/string data you can also send structured data, like json
, xml
, etc..
The next example will send two messages, one in json
format and one in text/plain
format:
{
use(new MyJsonRenderer());
sse("/path", sse -> {
MyObject object = ...
sse.send(object, "json");
sse.send(object, "plain");
});
}
Or if your need only one format, just:
{
use(new MyJsonRenderer());
sse("/path", sse -> {
MyObject object = ...
sse.send(object);
}).produces("json"); // json by default
}
request parameters
Request access is provided by a two argument callback:
{
sse("/events/:id", (req, sse) -> {
String id = req.param("id").value();
MyObject object = findObject(id);
sse.send(object);
});
}
connection lost
The sse.onClose(Runnable) callback allows you to clean and release resources on connection close. A connection is closed when you call the sse.close() method or when the remote client closes the connection.
{
sse("/events/:id", sse -> {
sse.onClose(() -> {
// clean up resources
});
});
}
A connection close event is detected and fired when you attempt to send some data.
keep alive time
The keep alive time feature can be used to prevent connections from timing out:
{
sse("/events/:id", sse -> {
sse.keepAlive(15, TimeUnit.SECONDS);
});
}
The previous example will send a ':'
message (empty comment) every 15 seconds to keep the connection alive. If the client drops the connection, then the sse.onClose(Runnable) event will be fired.
This feature is useful when you want to detect close events without waiting for the next time you send an event. If on the other hand your application already generates events every 15 seconds, the use of keep alive is unnecessary.
require
The sse.require(Type) methods gives you access to application services:
{
sse("/events/:id", sse -> {
MyService service = sse.require(MyService.class);
});
}
example
The next example will generate a new event every 60 seconds. It recovers from a server shutdown by using the sse.lastEventId() and cleans resources on connection close.
{
// creates an executor service
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
sse("/events", sse -> {
// if we go down, recover from last event ID we sent. Otherwise, start from zero.
int lastId = sse.lastEventId(Integer.class).orElse(0);
AtomicInteger next = new AtomicInteger(lastId);
// send events every 60s
ScheduledFuture<?> future = executor.scheduleAtFixedRate(() -> {
Integer id = next.incrementAndGet();
Object data = findDataById(id);
// send data and id
sse.event(data).id(id).send();
}, 0, 60, TimeUnit.SECONDS);
// on connection lost, cancel 60s task
sse.onClose(() -> {
future.cancel(true);
});
});
}
misc
cors
Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources (e.g. fonts, JavaScript, etc.) on a web page to be requested from another domain outside the domain from which the resource originated.
usage
{
use("*", new CorsHandler());
}
Previous example, will handle CORS request (simple or preflight).
Default options are defined via .conf
file:
cors {
# Configures the Access-Control-Allow-Origin CORS header. Possibly values: *, domain, regex or a list of previous values.
# Example:
# "*"
# ["http://foo.com"]
# ["http://*.com"]
# ["http://foo.com", "http://bar.com"]
origin: "*"
# If true, set the Access-Control-Allow-Credentials header
credentials: true
# Allowed methods: Set the Access-Control-Allow-Methods header
allowedMethods: [GET, POST]
# Allowed headers: set the Access-Control-Allow-Headers header. Possibly values: *, header name or a list of previous values.
# Examples
# "*"
# Custom-Header
# [Header-1, Header-2]
allowedHeaders: [X-Requested-With, Content-Type, Accept, Origin]
# Preflight max age: number of seconds that preflight requests can be cached by the client
maxAge: 30m
# Set the Access-Control-Expose-Headers header
# exposedHeaders: []
}
CORS
options are represented at runtime by the Cors handler.
xss
Cross-site scripting (XSS) is a type of security vulnerability typically found in web applications. XSS enables attackers to inject client-side scripts into web pages viewed by other users.
Jooby provides a few XSS escapers and a simple and flexible way to provide custom and/or more featured XSS escapers.
Default XSS escapers are urlFragment
, formParam
, pathSegment
and html
, all provided by Guava.
More advanced and feature rich escapers like js
, css
, sql
are provided via modules.
usage
There are a couple of ways to use XSS escape functions:
Applying an XSS escaper to param or header:
{
post("/", req -> {
String safeParam = req.param("input", "html").value();
String safeHeader = req.header("input", "html").value();
});
}
Here input
is the param/header
that you want to escape with the html
escaper.
Applying multiple XSS escapers:
{
post("/", req -> {
String safeInput = req.param("input", "urlFragment", "html");
});
}
Applying an XSS escaper to form/bean:
{
post("/", req -> {
MyForm form = req.params("input", "html");
});
}
Applying an XSS escaper from template engines
Template engines usually provide built in methods to escape HTML
. However, Jooby will also integrate its XSS escapers with the template engine of your choice:
{{xss input "js" "html"}}
{{xss (input, "js", "html")}}
${xss (input, "js", "html")}
jade:
p= xss.apply(input, "js", "html")
modules
- unbescape: XSS escapers via unbescape
- csl: XSS escapers via coverity-security-library