Introductory workshop about Ratpack.
Software requirements
Make sure you have:
-
JDK 8 (both in command line and in your IDE).
-
Git.
-
Gradle 2.6+.
-
Groovy 2.4.1+ (on the command line)
-
Lazybones installed via GVM.
You should have a similar output in your terminal:
$ java -version
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)
$ git --version
git version 2.3.2
$ gradle -v
------------------------------------------------------------
Gradle 2.9
------------------------------------------------------------
Build time: 2015-11-17 07:02:17 UTC
Build number: none
Revision: b463d7980c40d44c4657dc80025275b84a29e31f
Groovy: 2.4.4
Ant: Apache Ant(TM) version 1.9.3 compiled on December 23 2013
JVM: 1.8.0_60 (Oracle Corporation 25.60-b23)
OS: Mac OS X 10.11.1 x86_64
$ groovy -v
Groovy Version: 2.4.5 JVM: 1.8.0_60 Vendor: Oracle Corporation OS: Mac OS X
$ lazybones help
Lazybones is a command-line based tool for creating basic software projects from templates.
Available commands:
create Creates a new project from a template.
config Displays general help, or help for a specific command.
generate Generates new files in the current project based on a sub-template.
list Lists the templates that are available for you to install.
info Displays information about a template, such as latest version, description, etc.
help Displays general help, or help for a specific command.
Once done, you can clone this repo:
git clone https://github.com/alvarosanchez/ratpack-101.git
You will find each exercise’s template files on each exNN
folder. Solution is always inside a solution
folder.
Acknowledgments
-
Andrea Nagy for her valuable feedback about this workshop.
-
Wolfgang Schell for improvements to this guide.
-
Aldrin Misquitta for the upgrade to Ratpack 1.0
1. Hello World using a Groovy script (15 minutes)
The simplest way of creating a Ratpack application is using a Groovy script. Create a file named ratpack.groovy
with the following content:
@Grab('org.slf4j:slf4j-simple:1.7.12')
@Grab('io.ratpack:ratpack-groovy:1.1.1')
import static ratpack.groovy.Groovy.ratpack
ratpack {
handlers {
get(":name") {
render "Hello $pathTokens.name!"
}
get {
render "Hello World!"
}
}
}
What you see there is the Groovy DSL for Ratpack. Those not yet familiar with it, can try to guess what it’s defined there: 2 handlers for HTTP GET requests. We will explore handlers later.
Now let’s run that. Go to your terminal and run:
$ groovy ratpack.groovy
The first time you run it, it might take a while for Grape to resolve the dependencies.
You can use the JVM parameter -Dgroovy.grape.report.downloads=true to get output information about dependencies resolution.
|
Once it has finished, you can point your browser to http://localhost:5050 or http://localhost:5050/GR8Conf
2. Creating Ratpack projects with Lazybones and running them with Gradle and as standalone JAR (10 minutes)
Lazybones is a scaffolding tool to generate application skeletons for different projects, and it’s the recommended option for bootstrapping a Ratpack application.
Let’s create a Ratpack project in the current directory:
lazybones create ratpack .
After that, you can run the application with Gradle:
./gradlew run
2.1. Examining the project layout (5 minutes)
Once you have created the app, let’s have a look at it’s content:
-
build.gradle
is the main Gradle build file. -
src/main/groovy
is the package root for your Groovy classes (as in all Gradle/Maven applications). -
src/ratpack
contains Ratpack specific artifacts. The most important one isRatpack.groovy
which contains the Ratpack DSL. If you take a look at it, you will find it similar to the script we created in the previous exercise. -
src/test/groovy
will contain your Spock tests.
2.2. Generating a standalone fat JAR (5 minutes)
The build.gradle
file generated by Lazybones comes with the Shadow plugin preinstalled, which will allow you to generate a standalone fat JAR.
To do so:
./gradlew shadowJar
Then, you can run it by simply executing:
java -jar build/libs/*-all.jar
3. Working with handlers (30 minutes)
Ratpack is built on top of Netty, a non-blocking HTTP server. Once the Ratpack server is started, it will listen for incoming requests, and for each, will execute all the handlers defined in the handler chain.
Ratpack is not Servlet API-based, so don’t expect things HttpServletRequest or HttpSession . However, you can think of handlers as a mix of a Servlet and a Filter.
|
Handlers are functions composed in a handler chain. A handler can do one of the following:
-
Respond to the request.
-
Delegate to the next handler in the chain.
-
Insert more handlers in the chain and delegate to them.
Each handler receives a Context
object to interact with the request
/response
objects, the chain, etc. All the methods of the Context
are automatically available in your Ratpack.groovy
DSL:
ratpack {
handlers {
all {
response.send("Hello World") //Equivalent to context.response.send("Hello World")
}
}
}
Handlers are evaluated and executed in the order they are defined. Handlers without path specified should be placed at the bottom of the chain. |
Using the skeleton provided, work in the Ratpack.groovy
file to complete the following exercises.
3.1. Delegating to the next handler
Define a handler that prints out (println
) the requested path, and then delegates to the next handler.
You can test it by requesting some URL:
$ curl 0:5050/some/path
And you should see /some/path
in the server logs.
You will also see a 404 error, as there is no downstream handler defined yet. You can ignore it for the moment. |
3.2. Responding to a specific path
Define a handler that renders "bar"
when a request is sent to /foo
Then test it:
$ curl 0:5050/foo
bar
3.3. Grouping handlers by path
Define the next handlers nested in the path /api
.
3.3.1. Group handlers by HTTP method
Define 2 handlers for the path /api/methods
:
-
If the request is GET, the response is
GET /api/methods
. -
If the request is POST, the response is
POST /api/methods
.
|
To test them:
$ curl 0:5050/api/methods
GET /api/methods
$ curl -X POST 0:5050/api/methods
POST /api/methods
3.3.2. Defining variable parts in the path
Define a handler for the path /api/<USERNAME>
, where <USERNAME>
can be any String. The response must be Hello, <USERNAME>
To test it:
$ curl 0:5050/api/alvaro.sanchez
Hello alvaro.sanchez
3.3.3. Defining handlers in it’s own class
For other than simple examples, it is often a good practice to define handlers in their own class under src/groovy
.
Create a handler (implementing Handler
interface) for the path /api/now/
and respond with a new Date()
.
To test it:
$ curl 0:5050/api/now
Thu Nov 26 17:55:44 CET 2015
4. Testing Ratpack applications with Spock (20 minutes)
Write the following tests as specified under src/test/groovy
. To run them, you can just run ./gradlew clean test
4.1. Unit testing
Unit testing does not require any special infrastructure. Moreover, Ratpack helps you providing a RequestFixture
which can help you unit testing your handler.
It is particularly useful to have handlers in their own class, to be able to test them. For instance, you can use the following code to mock a JSON request to your handler, in a fluent API way:
HandlingResult result = RequestFixture.handle(new MyHandlerImpl()) { RequestFixture fixture ->
fixture.body('{"foo":"bar"}', 'application/json').header('X-Auth-Token', 'sdfgdsfgdsfgdfg')
}
There are other useful methods in RequestFixture
, like method()
to specify the HTTP method, registry()
to set values on the registry if your handler expects them and uri()
and pathBinding()
to specify the URL and the parameters in the request.
Have a look at the full documentation.
The returned value is of type HandlingResult
, on which you can make assertions about the result of the execution of the handler.
So, given the UsernameHandler
implementation, write a Spock unit test specification (UsernameHandlerSpec
) that tests it with both empty and non-empty strings.
|
4.2. Functional testing
Functional testing in the context of a Ratpack app means to spin up a server instance, use an HTTP client to send requests and set assertions on the response received.
To make Ratpack start the application your tests, and to get an HTTP client, you can define the following attributes in your specification class:
@Shared ApplicationUnderTest aut = new GroovyRatpackMainApplicationUnderTest()
@Delegate TestHttpClient testClient = aut.httpClient
Note that by using Groovy’s @Delegate
annotation, all the methods of the HTTP Client are available in your specification.
For this exercise, write a Spock specification (ApplicationSpec
) that tests all the handlers defined in the chain DSL (Ratpack.groovy
).
5. Working with the context (20 minutes)
The context is a place where handlers can register objects so that other handlers can get them in a later stage in the chain.
To build a Registry
, you can use the methods in the Registries
class:
handler {
//Builds a registry builder and adds 2 objects to it
Registry registry = Registries.registry().add(MyService, new MyServiceImpl()).add('String instance').build()
next(registry)
}
handler('foo') {
assert context.get(MyService) instanceof MyServiceImpl
assert context.get(String) == 'String instance'
}
Sometimes you just need to pass a single object to downstream handlers. Note also that alternatively, you can get access registry object by defining them as the closure argument of the handler:
handler {
String token = request.headers.get('X-Auth-Token')
next(just(token))
}
handler('foo') {
String token = context.get(String)
doWhatEver(token)
}
handler('bar') { String token ->
doWhatEver(token)
}
For this exercise, write the necessary handler chain in Ratpack.groovy
to make all the tests pass.
6. Using Ratpack modules with Google Guice (20 minutes)
As an optional feature, Ratpack offers you the possibility to enhance your application by using modules powered by Guice.
Google Guice is a dependency injection framework built by Google that allows you to decouple the components of your application by making them reusable parts. Ratpack also offers to you ready to use modules, such as Jackson, H2, Codahale Metrics, Handlebars, etc.
When using Gradle and the Ratpack Gradle plugin, adding a "supported" module is as easy as:
build.gradle
...
dependencies {
compile ratpack.dependency("h2")
}
...
Then, you have to add the H2 module to the binding:
ratpack {
bindings {
add new H2Module()
}
handlers {
...
}
}
With the Jackson module, you can benefit from the different ways of reading and writing JSON thanks to the static
methods of the Jackson
class:
handler {
render(Jackson.json(foo: 'bar')) // Yields {"foo":"bar"}
}
The ratpack-jackson-guice library was removed from 1.0.0 RC-1 onwards and
the functionality is now part of ratpack-core . So you don’t need to change your build.gradle file.
|
6.1. Rendering JSON with Jackson
Now the tests have been updated to expect a JSON response when /api/:username
endpoint is called. Make the necessary
changes to the application to make the test pass.