Fork me on GitHub

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

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 is Ratpack.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.