1. Overview
In this tutorial, we’ll be taking a tour of the lightweight Java REST framework RESTX.
2. Features
Building a RESTful API is quite easy with the RESTX framework. It has all the defaults that we can expect from a REST framework like serving and consuming JSON, query and path parameters, routing and filtering mechanisms, usage statistics, and monitoring.
RESTX also comes with an intuitive admin web console and command line installer for easy bootstrapping.
It’s also licensed under the Apache License 2 and maintained by a community of developers. The minimum Java requirement for RESTX is JDK 7.
3. Configuration
RESTX comes with a handy shell/command app which is useful to quickly bootstrap a Java project.
We need to install the app first before we can proceed. The detailed installation instruction is available here.
4. Installing Core Plugins
Now, it’s time to install the core plugins to be able to create an app from the shell itself.
In the RESTX shell, let’s run the following command:
shell install
It will then prompt us to select the plugins for installation. We need to select the number which points to io.restx:restx-core-shell. The shell will automatically restart once installation completes.
5. Shell App Bootstrap
Using the RESTX shell it is very convenient to bootstrap a new app. It provides a wizard-based guide.
We start by executing the following command on the shell:
app new
This command will trigger the wizard. Then we can either go with the default options or change them as per our requirements:
Since we have chosen to generate a pom.xml, the project can be easily imported into any standard Java IDE.
In a few cases, we may need to tweak the IDE settings.
Our next step will be to build the project:
mvn clean install -DskipTests
Once the build is successful we can run the AppServer class as a Java Application from the IDE. This will start the server with the admin console, listening on port 8080.
We can browse to http://127.0.0.1:8080/api/@/ui and see the basic UI.
The routes starting with /@/ are used for the admin console which is a reserved path in RESTX.
To log into the admin console we can use the default username “admin“ and the password we provided while creating the app.
Before we play around with the console, let’s explore the code and understand what the wizard generated.
6. A RESTX Resource
The routes are defined in <main_package>.rest.HelloResource class:
@Component
@RestxResource
public class HelloResource {
@GET("/message")
@RolesAllowed(Roles.HELLO_ROLE)
public Message sayHello() {
return new Message().setMessage(String.format("hello %s, it's %s",
RestxSession.current().getPrincipal().get().getName(),
DateTime.now().toString("HH:mm:ss")));
}
}
It’s immediately apparent that RESTX uses default J2EE annotations for security and REST bindings. For the most part, it uses its own annotations for dependency injection.
RESTX also supports many reasonable defaults for mapping method parameters to the request.
And, in addition to these standard annotations is @RestxResource, which declares it as a resource that RESTX recognizes.
The base path is added in the src/main/webapp/WEB-INF/web.xml. In our case, it’s /api, so we can send a GET request to http://localhost:8080/api/message, assuming proper authentication.
The Message class is just a Java bean that RESTX serializes to JSON.
We control the user access by specifying the RolesAllowed annotation using the HELLO_ROLE which was generated by the bootstrapper.
7. The Module Class
As noted earlier, RESTX uses J2EE-standard dependency injection annotations, like @Named, and invents its own where needed, likely taking a cue from the Dagger framework for @Module and @Provides.
It uses these to create the applications main module, which among other things, defines the admin password:
@Module
public class AppModule {
@Provides
public SignatureKey signatureKey() {
return new SignatureKey("restx-demo -44749418370 restx-demo 801f-4116-48f2-906b"
.getBytes(Charsets.UTF_8));
}
@Provides
@Named("restx.admin.password")
public String restxAdminPassword() {
return "1234";
}
@Provides
public ConfigSupplier appConfigSupplier(ConfigLoader configLoader) {
return configLoader.fromResource("restx/demo/settings");
}
// other provider methods to create components
}
@Module defines a class that can define other components, similar to @Module in Dagger, or @Configuration in Spring.
@Provides exposes a component programmatically, like @Provides in Dagger, or @Bean in Spring.
And, finally, the @Named annotation is used to indicate the name of the component produced.
AppModule also provides a SignatureKey used to sign content sent to the clients. While creating the session for the sample app, for example, this will set a cookie, signed with the configured key:
HTTP/1.1 200 OK
...
Set-Cookie: RestxSessionSignature-restx-demo="ySfv8FejvizMMvruGlK3K2hwdb8="; RestxSession-restx-demo="..."
...
And check out RESTX’s components factory/dependency injection documentation for more.
8. The Launcher Class
And lastly, the AppServer class is used to run the application as a standard Java app in an embedded Jetty server:
public class AppServer {
public static final String WEB_INF_LOCATION = "src/main/webapp/WEB-INF/web.xml";
public static final String WEB_APP_LOCATION = "src/main/webapp";
public static void main(String[] args) throws Exception {
int port = Integer.valueOf(Optional.fromNullable(System.getenv("PORT")).or("8080"));
WebServer server =
new Jetty8WebServer(WEB_INF_LOCATION, WEB_APP_LOCATION, port, "0.0.0.0");
System.setProperty("restx.mode", System.getProperty("restx.mode", "dev"));
System.setProperty("restx.app.package", "restx.demo");
server.startAndAwait();
}
}
Here, the dev mode is used during the development phase to enable features such as auto-compile which shortens the development feedback loop.
We can package the app as a war (web archive) file to deploy in a standalone J2EE web container.
Let’s find out how to test the app in the next section.
9. Integration Testing Using Specs
One of the strong features of RESTX is its concept of “specs”. A sample spec would look like this:
title: should admin say hello
given:
- time: 2013-08-28T01:18:00.822+02:00
wts:
- when: |
GET hello?who=xavier
then: |
{"message":"hello xavier, it's 01:18:00"}
The test is written in a Given-When-Then structure within a YAML file which basically defines how the API should respond (then) to a specific request (when) given a current state of the system (given).
The HelloResourceSpecTest class in src/test/resources will trigger the tests written in the specs above:
@RunWith(RestxSpecTestsRunner.class)
@FindSpecsIn("specs/hello")
public class HelloResourceSpecTest {}
The RestxSpecTestsRunner class is a custom JUnit runner. It contains custom JUnit rules to:
- set up an embedded server
- prepare the state of the system (as per the given section in the specs)
- issue the specified requests, and
- verify the expected responses
The @FindSpecsIn annotation points to the path of the spec files against which the tests should be run.
The spec helps to write integration tests and provide examples in the API docs. Specs are also useful to mock HTTP requests and record request/response pairs.
10. Manual Testing
We can also test manually over HTTP. We first need to log in, and to do this, we need to hash the admin password in the RESTX console:
hash md5 <clear-text-password>
And then we can pass that to the /sessions endpoint:
curl -b u1 -c u1 -X POST -H "Content-Type: application/json"
-d '{"principal":{"name":"admin","passwordHash":"1d528266b85cf052803a57288"}}'
http://localhost:8080/api/sessions
(Note that Windows users need to download curl first.)
And now, if we use the session as part of our /message request:
curl -b u1 "http://localhost:8080/api/message?who=restx"
Then we’ll get something like this:
{"message" : "hello admin, it's 09:56:51"}
11. Exploring the Admin Console
The admin console provides useful resources to control the app.
Let’s take a look at the key features by browsing to http://127.0.0.1:8080/admin/@/ui.
11.1. API Docs
The API docs section lists all available routes including all the options:
And we can click on individual routes and try them out on the console itself:
11.2. Monitoring
The JVM Metrics section shows the application metrics with active sessions, memory usage, and thread dump:
Under Application Metrics we have mainly two categories of elements monitored by default:
- BUILD corresponds to the instantiation of the application components
- HTTP corresponds to HTTP requests handled by RESTX
11.3. Stats
RESTX lets the user choose to collect and share anonymous stats on the application to give information to the RESTX community. We can easily opt out by excluding the restx-stats-admin module.
The stats report things like the underlying OS and the JVM version:
Because this page shows sensitive information, make sure to review its configuration options.
Apart from these, the admin console can also help us:
- check the server logs (Logs)
- view the errors encountered (Errors)
- check the environment variables (Config)
12. Authorization
RESTX endpoints are secured by default. That means if for any endpoint:
@GET("/greetings/{who}")
public Message sayHello(String who) {
return new Message(who);
}
When called without authentication will return a 401 by default.
To make an endpoint public, we need to use the @PermitAll annotation either at the method or class level:
@PermitAll
@GET("/greetings/{who}")
public Message sayHello(String who) {
return new Message(who);
}
Note that at the class level, all methods are public.
Further, the framework also allows specifying user roles using the @RolesAllowed annotation:
@RolesAllowed("admin")
@GET("/greetings/{who}")
public Message sayHello(String who) {
return new Message(who);
}
With this annotation, RESTX will verify if the authenticated user also has an admin role assigned. In case an authenticated user without admin roles tries to access the endpoint, the application will return a 403 instead of a 401.
By default, the user roles and credentials are stored on the filesystem, in separate files.
So, the user id with the encrypted password is stored under /data/credentials.json file:
{
"user1": "$2a$10$iZluUbCseDOvKnoe",
"user2": "$2a$10$oym3Swr7pScdiCXu"
}
And, the user roles are defined in /data/users.json file:
[
{"name":"user1", "roles": ["hello"]},
{"name":"user2", "roles": []}
]
In the sample app, the files get loaded in the AppModule via the FileBasedUserRepository class:
new FileBasedUserRepository<>(StdUser.class, mapper,
new StdUser("admin", ImmutableSet.<String> of("*")),
Paths.get("data/users.json"), Paths.get("data/credentials.json"), true)
The StdUser class holds the user objects. It can be a custom user class but it needs to be serializable into JSON.
We can, of course, use a different UserRepository implementation, like one that hits a database.
13. Conclusion
This tutorial gave an overview of the lightweight Java-based RESTX framework.
The framework is still in development and there might be some rough edges using it. Check out the official documentation for more details.
The sample bootstrapped app is available in our GitHub repository.