Flak is a minimal but powerful framework that leverages the HttpServer embedded in the JDK. Its main philosophy is keeping boilerplate to a minimum.
It is composed of a generic API, a default implementation and some add-ons.
In a minimal setup, the total size of dependencies is around 40KiB. If you
need to implement a REST server and handle JSON data, you will have to add
jackson-databind
to your dependencies.
Flak components | Description |
---|---|
flak-api |
Public API |
flak-spi |
Internal API for service providers |
flak-backend-jdk |
Binding for the web server included in JDK |
flak-login |
Add-on for managing authentication |
flak-resource |
Add-on for serving static resources |
flak-jackson |
Add-on for conversion to/from JSON using jackson |
flak-swagger |
Add-on to dynamically generate OpenAPI specifications |
Here is the obligatory HelloWorld application.
Here is the minimal set of dependencies needs to be included in build.gradle
.
repositories {
maven { url "https://jitpack.io" }
}
dependencies {
compile "com.github.pcdv.flak:flak-api:2.7.0"
runtime "com.github.pcdv.flak:flak-backend-jdk:2.7.0"
}
The following application outputs "Hello world!" on http://localhost:8080
:
public class HelloWorld {
@Route("/")
public String helloWorld() {
return "Hello world!";
}
public static void main(String[] args) throws Exception {
App app = Flak.createHttpApp(8080);
app.scan(new HelloWorld());
app.start();
Desktop.getDesktop().browse(new URI(app.getRootUrl()));
}
}
Or if you like it compact:
public class HelloWorldCompact {
public static void main(String[] args) throws Exception {
Flak.createHttpApp(8080).scan(new Object() {
@Route("/")
public String helloWorld() {
return "Hello world!";
}
}).start();
}
}
A route handler is a public method annotated with @Route. As in Python Flask, the route's argument specifies the path to which the handler must be bound (relative to the root path of the application).
It is associated with only one HTTP method which is GET by default. To associate it with another method, just add the @Post, @Put, @Delete or any other annotation.
Route handlers can be defined in any class. Scan an instance of the class with App.scan() so all handlers can be discovered.
Route handlers can return the following basic types:
String
: directly returned in responsebyte[]
: directly returned in responseInputStream
: piped into responsevoid
: returns an empty document
You can return any other type provided an OutputFormatter is specified. Use the @OutputFormat annotation to specify which formatter to use.
Note that the formatter is referenced by name and needs to have been registered
before with App.addOutputFormatter()
.
If what you need is to convert the returned object to JSON, you can simply
use the Jackson
plugin and add the @JSON
annotation.
Route handlers can accept arguments. Like with Flask, arguments can be extracted from the request's path. But there is more.
If the path contains variable (e.g. /api/:arg1/:arg2
), they are
automatically split, converted and passed as method arguments. The route handler
must have the same number of int
or String
arguments. For example:
@Route("/db/hello/:name")
public String hello(String name) {
return "Hello " + name;
}
Each HTTP call is wrapped in a Request. You can access the request by simply adding a Request argument in your method, e.g.
@Route("/api/stuff")
public String getStuff(Request req) {
return "You submitted param1=" + req.getQuery().get("param1");
}
If you only need to access the query string, the above example can be simplified to:
@Route("/api/stuff")
public String getStuff(Query q) {
return "You submitted param1=" + q.get("param1");
}
The query corresponds to arguments that are present in request URL, after '?',
e.g. /api/stuff?param1=42
Note that from version 2.7.0, you can also do:
@Route("/api/stuff")
public String getStuff(@QueryParam("param1") String p1) {
return "You submitted param1=" + p1;
}
One advantage of this style is that the OpenAPI generator can automatically take into account the parameter without additional boilerplate.
Similar to the example above, if you are in a POST route handler and need to
access arguments in application/x-www-form-urlencoded
format, you can use
a Form
argument.
See the following example.
You can accept other argument types if you:
- associate the type with an extractor using method AbstractApp.addCustomExtractor() (this is not in official API yet)
- specify an input format with the @InputFormat annotation (which requires prior declaration of an InputParser with App.addInputParser().
- a common case is to decode an object serialized as JSON in the body of the request. You could do the following:
@Route("/api/jsonMap")
@Post
@JSON
public Map postMap(Map map) {
map.put("status", "ok");
return map;
}
Gzip compression can be enabled for a given endpoint or all endpoints of a
class by using the @Compress
annotation. Alternatively, it can be enabled
using method Response.setCompressionAllowed(true)
.
Files served with FlakResourceImpl
will be automatically compressed
according to their content type and size.
It is possible to tune compression behavior:
- file size threshold (using system property
flak.compressThreshold
) - eligible content types (using a custom
ContentTypeProvider
)
The example above allocates a web server for a single application. However,
it is possible to host several Flak apps on a single server, for example
one located at path /app1
and another one at /app2
.
The idea is to create a FlakFactory
then call createApp(String)
with two separate paths. Then you can add your
route handlers and start them.
Other features that still need to be documented (until more documentation is available, you can find examples in the junits):
- easy parsing of path arguments (e.g.
/api/todo/:id
or/api/upload/*path
) - error handlers
- HTTP redirection
- pluggable user authentication
- direct serving of static resources from a directory or jar
- HTTPS support (experimental)
- ...
I'm a big fan of lightweight and simple. I've always liked the simplicity of Flask applications and missed an equivalent solution for Java. Most existing frameworks were very heavy in terms of dependencies (e.g. Play, Spring Boot, etc). Spark was a better fit but it brings ~2.5MiB of dependencies.
The JDK includes a HTTP server that is perfectly suited for serving small applications but its API is rather painful. Flak allows to leverage it with a friendly API and in the future will support other back-ends.
The API initially shared a lot of similarities with Flask:
- route handlers are methods with annotations like
@Route
,@Post
,@LoginRequired
etc. - the request can be accessed through a ThreadLocal
- user authentication is similar to flask-login
But now the style differs quite a bit since objects can be automatically passed in method arguments.
Flak is a refactored fork of JFlask.
- have a clean API, well separated from implementation
- provide several back-ends (only one is available at this time: flak-backend-jdk but it will now be possible to provide backends for Netty, Jetty, etc.)
- provide SSL support
- optional plugins for user management, JSON serialization, CSRF protection...
If your project uses the local Ivy repository, run:
./gradlew publish -Pversion=3.0-SNAPSHOT
If your project uses the local Maven repository, run:
./gradlew publishToMavenLocal -Pversion=3.0-SNAPSHOT
Then use version 3.0-SNAPSHOT
in your project dependencies.