This manual is a work in progress and is currently incomplete.
If you'd like to help improve it, and we hope you do, please see the README.

1 Introduction

Ratpack is a toolkit for creating web applications that run on the JVM. It provides a structure in which to write code that responds to HTTP requests. It is not a full stack framework so does not prescribe the use of any other particular framework for other web application concerns such as security, persistence, marshalling etc. There are however optional add on libraries that integrate specific tools, and integrating a new tool or framework is typically very simple.

Ratpack applications are not Java Servlet applications. They do not deploy to Java application servers. Ratpack builds on Netty, which is a high performance IO engine. Application deployments are self contained JVM applications.

Ratpack tries to strike the balance between provide helpful abstractions and infrastructure, while not being too prescriptive. It aims to make it easy to write small applications, yet not get in the way as your application evolves and becomes more complex

1.1 Highlights

Here are some key highlights:

Read on for details.

1.2 How to read the documentation

The canonical reference documentation for Ratpack is the Javadoc. This manual acts as a starting point for different aspects of Ratpack and as a mechanism to pull together larger ideas and patterns. The manual frequently introduces topics at a high level and links through to the Javadoc for detailed API information. Much work goes in to making the Javadoc (hopefully) useful.

1.3 Project Status

Ratpack is a new project, but stands on the shoulders of well established technologies and is built by developers with substantial experience in web application frameworks and tooling. It is currently pre 1.0 but under very active development.

No API compatibility guarantees are provided before the 1.0 release, from which point on the API will be strictly semantically versioned.

While it is new, it has received many man hours of development time.

2 Setup

Ratpack is purely a runtime. There is no installable package. To build Ratpack applications, you can use any JVM build tool. The Ratpack project provides specific support for Gradle through plugins, but any could be used.

Ratpack is published as a set of library JARs. The ratpack-core library is the only strictly required library. Others such as ratpack-groovy, ratpack-guice, ratpack-jackson, ratpack-test etc. are optional.

With Ratpack on the classpath, you can use the API described in the next chapter to launch the application.

2.1 Using the Gradle plugin(s)

TODO

2.2 Using Lazybones project templates

Lazybones is a command line tool that allows you to generate a project structure for any framework based on pre-defined templates.

Ratpack’s Lazybones templates can be found on Bintray in the ratpack/lazybones repository. Templates are published with each Ratpack release and template versions are aligned with Ratpack release versions. There are different types of Ratpack templates available, listed in the repository. See the description of each for details.

See the Lazybones documentation for help with installing Lazybones.

Lazybones commands are in the format…

lazybones create <ratpack template> <ratpack version> <app name>

With Lazybones installed, creating a new Ratpack application is as easy as…

lazybones create ratpack my-ratpack-app
cd my-ratpack-app
./gradlew run

This will use the latest available version of Ratpack. If a specific version is required…

lazybones create ratpack 0.9.0 my-ratpack-app
cd my-ratpack-app
./gradlew run

3 Launching

This chapter introduces how to launch a Ratpack application, and the associated launch time configuration.

3.1 Launch configuration

To launch a Ratpack application, you must create a LaunchConfig. This can then be used with the RatpackServerBuilder method to create a RatpackServer that can then be started.

Note that one of the LaunchConfig object’s responsibilities is to provide a HandlerFactory implementation. This factory is responsible for creating the handler that is effectively the Ratpack application. See the chapter on handlers for more details.

One option for building a LaunchConfig is to use the LaunchConfigBuilder. Another option is to use LaunchConfigs which is able to build a launch config from system properties and a properties file.

3.2 RatpackMain

The RatpackMain class is a convenient entry point (i.e. main class) that bootstraps a Ratpack application. When building with Gradle, this is the default main class.

The minimum requirement for launching a RatpackMain based application, is to ensure there is a ratpack.properties file on the JVM classpath that indicates the application HandlerFactory

handlerFactory=org.my.HandlerFactory

All aspects of the LaunchConfig can be specified in this properties file. Aspects can also be environmentally overridden by JVM system properties. See RatpackMain for details.

4 Handlers

This chapter introduces handlers, which are the fundamental components of a Ratpack application.

4.1 What is a handler?

Conceptually, a handler (Handler) is just a function that acts on a handling context (Context).

The “hello world” handler looks like this…

import ratpack.handling.Handler;
import ratpack.handling.Context;

public class HelloWorld implements Handler {
  public void handle(Context context) {
      context.getResponse().send("Hello world!");
  }
}

As we saw in the previous chapter, one of the mandatory launch config properties is the HandlerFactory implementation that provides the primary handler. The handler that this factory creates is effectively the application.

This may seem limiting, until we recognise that a handler does not have to be an endpoint (i.e. it can do other things than generate a HTTP response). Handlers can also delegate to other handlers in a number of ways, serving more of a routing function. The fact that there is no framework level (i.e. type) distinction between a routing step and an endpoint offers much flexibility. The implication is that any kind of custom request processing pipeline can be built by composing handlers. This compositional approach is the canonical example of Ratpack’s philosophy of being a toolkit instead of a magical framework.

The rest of this chapter discusses aspects of handlers that are beyond HTTP level concerns (e.g. reading headers, sending responses etc.), which is addressed in the HTTP chapter.

4.2 Handler delegation

If a handler is not going to generate a response, it must delegate to another handler. It can either insert one or more handlers, or simply defer to the next handler.

Consider a handler that routes to one of two different handlers based on the request path. This can be implemented as…

import ratpack.handling.Handler;
import ratpack.handling.Context;

public class FooHandler implements Handler {
  public void handle(Context context) {
    context.getResponse().send("foo");
  }
}

public class BarHandler implements Handler {
  public void handle(Context context) {
    context.getResponse().send("bar");
  }
}

public class Router implements Handler {
  private final Handler fooHandler = new FooHandler();
  private final Handler barHandler = new BarHandler();
      
  public void handle(Context context) {
    String path = context.getRequest().getPath();
    if (path.equals("foo")) {
      context.insert(fooHandler);
    } else if (path.equals("bar")) {
      context.insert(barHandler);
    } else {
      context.next();
    } 
  }    
}

The key to delegation is the context.insert() method that passes control to one or more linked handlers. The context.next() method passes control to the next linked handler.

Consider the following…

import ratpack.handling.Handler;
import ratpack.handling.Context;

public class PrintThenNextHandler implements Handler {
  private final String message;
  
  public PrintThenNextHandler(String message) {
    this.message = message;
  } 
  
  public void handle(Context context) {
    System.out.println(message);
    context.next();
  }
}

public class Application implements Handler {    
  public void handle(Context context) {
    context.insert(
      new PrintThenNextHandler("a"),
      new PrintThenNextHandler("b"),
      new PrintThenNextHandler("c")
    );
  }    
}

Given that Application is the primary handler (i.e. the one returned by the launch config’s HandlerFactory), when this application receives a request the following will be written to System.out

a
b
c

And then what? What happens when the “c” handler delegates to its next? The last handler is always an internal handler that issues a HTTP 404 client error (via context.clientError(404) which is discussed later).

Consider that inserted handlers can themselves insert more handlers…

import ratpack.handling.Handler;
import ratpack.handling.Context;

public class PrintThenInsertOrNextHandler implements Handler {
  private final String message;
  private final Handler[] handlers;

  public PrintThenInsertOrNextHandler(String message, Handler... handlers) {
    this.message = message;
    this.handlers = handlers;
  }

  public void handle(Context context) {
    System.out.println(message);
    if (handlers.length == 0) {
      context.next();
    } else {
      context.insert(handlers);
    }
  }
}

public class Application implements Handler {
  public void handle(Context context) {
    context.insert(
      new PrintThenInsertOrNextHandler("a",
        new PrintThenInsertOrNextHandler("a.1"),
        new PrintThenInsertOrNextHandler("a.2"),
      ),
      new PrintThenInsertOrNextHandler("b",
        new PrintThenInsertOrNextHandler("b.1",
          new PrintThenInsertOrNextHandler("b.1.1")
        ),
      ),
      new PrintThenInsertOrNextHandler("c")
    );
  }
}

This would write the following to System.out

a
a.1
a.2
b
b.1
b.1.1
c

This demonstrates how the next handler of the handler that inserts the handlers becomes the next handler of the last of the inserted handlers. You might need to read that sentence more than once.

You should be able to see a certain nesting capability emerge. This is important for composibility, and also for scoping which will be important when considering the registry context later in the chapter.

It would be natural at this point to think that it looks like a lot of work to build a handler structure for a typical web application (i.e. one that dispatches requests matching certain request paths to endpoints). Read on.

4.3 Building handler chains

Ratpack provides a suite of routing type handlers out of the box that make it easy to compose dispatch logic. These are available via the static methods of the Handlers class.

For example, the path(String, Handler) method can be used for path based routing.

import ratpack.handling.Handler;
import ratpack.handling.Context;
import ratpack.launch.LaunchConfig;
import ratpack.launch.HandlerFactory;

import static ratpack.handling.Handlers.path;
import static ratpack.handling.Handlers.get;
import static ratpack.handling.Handlers.chain;

public class SomeHandler implements Handler {
  public void handle(Context context) {
      // do some application work
  }
}

public class Application implements HandlerFactory {
  public Handler create(LaunchConfig launchConfig) {
    return path("foo/bar", chain(get(), new SomeHandler()));
  }
}

Here we have a HandlerFactory that can be used when launching an app (see previous chapter). For this “application”:

  1. a GET request to /foo/bar would be routed to the SomeHandler
  2. a non-GET request to /foo/bar would produce a HTTP 405 (method not allowed)
  3. anything else would produce a HTTP 404

This is easier than doing it all yourself, but we can do better. We can use the chain() method and the Chain DSL.

import ratpack.handling.Handler;
import ratpack.handling.Context;
import ratpack.handling.ChainAction;
import ratpack.func.Action;
import ratpack.launch.LaunchConfig;
import ratpack.launch.HandlerFactory;

import static ratpack.handling.Handlers.chain;

public class Application implements HandlerFactory {
  public Handler create(LaunchConfig launchConfig) {
    return chain(launchConfig, new ChainAction() {
      protected void execute() {
        prefix("api", new ChainAction() {
          protected void execute() {
            delete("someResource", new Handler() {
              public void handle(Context context) {
                // delete the resource
              }
            });
          }
        });

        assets("public");

        get("foo/bar", new Handler() {
          public void handle(Context context) {
            // do stuff
          }
        });
      }
    });
  }
}

(note: the use of inner classes adds a lot of syntactic bloat here, things are more concise with Java 8 lambdas)

The chain DSL is built on the existing delegation methods that have been presented so far. It is merely syntactic sugar. The Groovy version of this DSL is extremely sweet…

import ratpack.handling.Handler
import ratpack.launch.LaunchConfig
import ratpack.launch.HandlerFactory

import static ratpack.groovy.Groovy.chain

class Application implements HandlerFactory {
  Handler create(LaunchConfig launchConfig) {
    chain(launchConfig) {
      prefix("api") {
        delete("someResource") {
          // delete the resource
        }
      }
      assets("public")
      get("foo/bar") {
        // do stuff
      }
    }
  }
}

See the chapter on Groovy for more information on using Groovy with Ratpack.

5 Context

The Context type is at the core of Ratpack.

It provides:

For working directly with the request/response, see the HTTP chapter.

For information about delegation, see the Handlers chapter.

5.1 Contextual objects

The context is a registry. It provides access to via-type-lookup of objects that were made available upstream in the handler pipeline. This is the mechanism for inter-handler collaboration in Ratpack.

Consider the following example:

import ratpack.handling.Handler;
import ratpack.handling.Context;
import ratpack.handling.ChainAction;
import ratpack.launch.HandlerFactory;
import ratpack.launch.LaunchConfig;

import static ratpack.registry.Registries.just
import static ratpack.handling.Handlers.chain;

// The api of some business object
interface Person {}

// Implementation
public class PersonImpl implements Person {
  private final String id;
  public PersonImpl(String id) {
    this.id = id;
  };
}

public class Application implements HandlerFactory {
  public Handler create(LaunchConfig launchConfig) {
    return chain(launchConfig, new ChainAction() {
      protected void execute() {
        prefix("person/:id", new ChainAction() {
          protected void execute() {
            handler(new Handler() {
              public void handle(Context context) {
                String id = context.getPathTokens().get("id"); // (1)
                Person person = new PersonImpl(id);
                context.next(just(Person.class, person)); // (2)
              }
            });
            get("status", new Handler() {
              public void handle(Context context) {
                Person person = context.get(Person.class); // (3)
                // show the person's status
              }
            });
            get("age", new Handler() {
              public void handle(Context context) {
                Person person = context.get(Person.class); // (4)
                // show the person's age
              }
            });
          }
        });
      }
    });
  }
}

At (2) we are pushing the Person instance into the registry for the downstream handlers to use and at (3) and (4) how they retrieve it. We are decoupling the creation detail from the usage, and avoiding duplicating the creation code in the status and age handlers. The benefit of avoiding duplication is obvious. What’s slightly more subtle is that the decoupling makes testing easier when the downstream handlers are not implemented as anonymous classes (see the Testing chapter for for information).

At (1) we are also using contextual objects. The prefix() chain method binds on a request path, potentially capturing tokens. If the binding is successful, a PathBinding object is registered with the context that describes the binding result. This includes any path tokens that were captured as part of the binding. In the case above, we are capturing the second path component as the id. The getPathTokens() method on a context is literally shorthand for get(PathBinding.class).getPathTokens() on the same context. This is another example of using the context object mechanism for inter-handler communication.

Another example of using contextual objects is the shorthand for accessing files from the file system. Consider the following script, which makes use of the context’s file method to retrieve a static asset from the file system:


import static ratpack.groovy.Groovy.ratpack ratpack { handlers { get { def f = file('../') render f ?: "null-value" } } }

In the above example, the context’s file() method is being called to retrieve a java.io.File instance for the provided path. The context’s file() method is a shorthand to retrieve the FileSystemBinding object from the registry, and literally is a shorthand to get(FileSystemBinding.class).file(path/to/file). The context will always resolve file assets relative to the application root, so in the case where an absolute path is provided, it should be noted that the path to the asset will be prefixed by the path in which the application exists. For example, if your application exists in /home/ratpack/app and your handler uses the file method to resolve /etc/passwd, then the actual path that is resolved will be /home/ratpack/app/etc/passwd. In the case where a file cannot be resolved from within the application’s root, the file() method may return a null value, which is demonstrated in the above example. The developer is responsible for handling scenarios where accessing a file may return a null object.

5.1.1 Partitioning

The context object mechanism supports partitioning application logic by providing different objects to different partitions. This is because objects registered with context are implicitly scoped, depending on how they were registered. Objects registered with the next() methods are available to all downstream handlers that were part of the same insertion (i.e. context.insert() including and nested insertions. Objects registered with the insert() methods are available to the inserted handlers and nested insertions.

A typical use for this is using different error handling strategies for different parts of your application.

import ratpack.handling.Handler;
import ratpack.handling.Context;
import ratpack.handling.ChainAction;
import ratpack.launch.HandlerFactory;
import ratpack.launch.LaunchConfig;
import ratpack.error.ServerErrorHandler;

import static ratpack.handling.Handlers.chain;
import static ratpack.registry.Registries.just;

public class ApiHandlers extends ChainAction {
  protected void execute() {
    // add api handlers
  }
}

public class ApiServerErrorHandler implements ServerErrorHandler {
  public void error(Context context, Exception exception) {
    // return error data object
  }
}

public class AppHandlers extends ChainAction {
  protected void execute() {
    // add normal app handlers
  }
}

public class AppServerErrorHandler implements ServerErrorHandler {
  public void error(Context context, Exception exception) {
    // display pretty error page
  }
}

public class Application implements HandlerFactory {
  public Handler create(final LaunchConfig launchConfig) {
    return chain(launchConfig, new ChainAction() {
      protected void execute() {
        prefix("api", new ChainAction() {
          protected void execute() {
            register(just(ServerErrorHandler.class, new ApiServerErrorHandler()), new ApiHandlers());
          }
        });
        register(just(ServerErrorHandler.class, new AppServerErrorHandler()), new AppHandlers());
      }
    });
  }
}

6 Basic HTTP

This chapter introduces how to deal with basic HTTP concerns such as parsing requests, rendering responses, content negotiation file uploads etc.

6.1 Request & Response

The context object that a handler operates on provides the (getRequest() & getResponse() methods for accessing the Request and Response respectively. These objects provide more or less what you would expect.

For example, they both provide a getHeaders() method that returns an model of the HTTP headers sent with the request and a model of the HTTP headers that are to be sent with the response. The Request exposes other metadata attributes such as the HTTP method, the URI and a key/value model of the query string parameters among other things.

6.2 Reading the request

TODO introduce parsers generally

6.2.1 Forms

Ratpack provides a parser for Form objects in the core. This can be used for reading POST’d (or PUT’d etc. for that matter) forms, both URL encoded and multi part (including file uploads).

Here’s an example of using this from Java…

import ratpack.handling.Handler;
import ratpack.handling.Context;
import ratpack.form.Form;
import ratpack.form.UploadedFile;

public class MyHandler implements Handler {
  public void handle(Context context) {
    Form form = context.parse(Form.class);

    // Get the first attribute sent with name “foo”
    String foo = form.get("foo");

    // Get all attributes sent with name “bar”
    List<String> bar = form.getAll("bar");

    // Get the file uploaded with name “myFile”
    UploadedFile myFile = form.file("myFile");

    // Send back a response …
  }
}

See Form and UploadedFile for more information and examples.

6.3 Sending a response

TODO introduce status methods

TODO introduce send methods

TODO introduce renderers

TODO introduce sendFile methods (pointing to use of render(file(«path»))) instead.

6.4 Cookies

TODO introduce getCookies() on request and response

TODO introduce Request#oneCookie()

TODO introduce Response#expireCookie()

6.5 Sessions

TODO introduce ratpack-sessions library

7 Asynchronous & Non Blocking

Ratpack is designed for “asynchronous” & “non blocking” request processing. Its internal IO (e.g. HTTP request and response transfer) is all performed in a non blocking fashion (thanks to Netty). This approach yields higher throughput, lower resource usage, and importantly, more predictable behaviour under load. This programming model has become increasingly popular of late due to the Node.js platform. Ratpack is built on the same non blocking, event driven, model as Node.js.

Asynchronous programming is notoriously tricky. One of Ratpack’s key value propositions is that it provides constructs and abstractions to tame the asynchronous beast, yielding better performance while keeping implementation simple.

7.1 Comparison to blocking frameworks & containers

The Java Servlet API, that underpins most JVM web frameworks and containers, along with the majority of the JDK is fundamentally based on a synchronous programming model. Most JVM programmers are very familiar and comfortable with this programming model. In this model, when IO needs to be performed the calling thread will simply sleep until the operation is complete and the result is available. This model requires a reasonably large pool of threads. In a web application context, this usually means that each request is bound to a thread from the large pool and that the application can process «X» number of parallel requests, where «X» is the size of the thread pool.

Version 3.0 of the Servlet API does facilitate asynchronous request processing. However, retrofitting asynchronous support as an opt-in option is a different proposition to a completely asynchronous approach. Ratpack is asynchronous from the ground up.

The benefit of this model is that synchronous programming is unarguably “simpler”. The drawback of this model, opposed to a non blocking model, is that it demands greater resource usage and yields lower throughput. In order to serve more requests in parallel, the size of the thread pool has to be increased. This creates more contention for compute resources and more cycles are lost to managing the scheduling of these threads, not to mention the increased memory consumption. Modern operating systems, and the JVM, are very good at managing this contention; however, it is still a scaling bottleneck. Moreover, it demands greater resource allocation, which is a serious consideration for modern pay-for-what-you-use deployment environments.

The asynchronous, non blocking, model does not require a large thread pool. This is possible because threads are never blocked waiting for IO. If IO needs to be performed, the calling thread registers a callback of some sort that will be invoked when the IO is done. This allows the thread to be used for other processing while the IO is occurring. Under this model, the thread pool is sized according to the number of processing cores available. Since the threads are always busy with computation, there is no point in having more threads.

Many Java APIs (InputStream, JDBC, etc.) are predicated on a blocking IO model. Ratpack provides a mechanism for using such API while minimizing the blocking cost (discussed below).

Ratpack is fundamentally asynchronous in two key ways…

  1. HTTP IO is event driven / non blocking (thanks to Netty)
  2. Request handling is organised as a pipeline of asynchronous functions

The HTTP IO being event driven is largely transparent when using Ratpack. Netty just does its thing.

The second point is the key characteristic of Ratpack. It does not expect your code to be synchronous. Many web frameworks that have opt in asynchronous support have serious constraints and gotchas that become apparent when trying to perform complex (i.e. real world) async operations. Ratpack is asynchronous from the ground up. Moreover, it provides constructs and abstractions that facilitate complex asynchronous processing.

7.2 Performing blocking operations (e.g. IO)

Most applications are going to have to perform some kind of blocking IO. Many Java APIs do not offer asynchronous options (e.g. JDBC). Ratpack provides a simple mechanism for executing blocking operations in a separate thread pool. This avoids blocking request processing (i.e. compute) threads (which is a good thing), but does incur some overhead due to thread contention. If you have to use blocking IO APIs there is unfortunately no other option.

Let’s consider a contrived data store API. It is conceivable that communication with the actual data store requires IO (or if it is in memory, then its access requires waiting on one or more locks which has the same blocking effect). The API methods cannot be called on a request processing thread because they will block. Instead, we need to use the “blocking” API…

import ratpack.handling.InjectionHandler;
import ratpack.handling.Context;
import ratpack.func.Action;

import ratpack.test.UnitTest;
import ratpack.test.handling.HandlingResult;
import ratpack.test.handling.RequestFixtureAction;

import java.util.concurrent.Callable;
import java.util.Collections;
import java.io.IOException;

public class Example {

  // Some API that performs blocking operations
  public static interface Datastore {
    int deleteOlderThan(int days) throws IOException;
  }

  // A handler that uses the API
  public static class DeletingHandler extends InjectionHandler {
    void handle(final Context context, final Datastore datastore) {
      final int days = context.getPathTokens().asInt("days");
      context.blocking(new Callable<Integer>() {
        public Integer call() throws IOException {
          return datastore.deleteOlderThan(days);
        }
      }).then(new Action<Integer>() {
        public void execute(Integer result) {
          context.render(result + " records deleted");
        }
      });
    }
  }

  // Unit test
  public static void main(String[] args) {
    HandlingResult result = UnitTest.handle(new DeletingHandler(), new RequestFixtureAction() {
      protected void execute() {
        pathBinding(Collections.singletonMap("days", "10"));
        getRegistry().add(Datastore.class, new Datastore() {
          public int deleteOlderThan(int days) throws IOException {
            return days;
          }
        });
      }
    });

    assert result.rendered(String.class).equals("10 records deleted");
  }
}

The use of anonymous inner classes adds some syntax weight here. However, this API is very Java 8 lambda syntax friendly.

In Groovy, it’s much nicer…

import ratpack.groovy.handling.GroovyHandler
import ratpack.groovy.handling.GroovyContext
import ratpack.func.Action
import java.util.concurrent.Callable

public interface Datastore {
  int deleteOlderThan(int days) throws IOException
}

class DeletingHandler extends GroovyHandler {
  void handle(GroovyContext context) {
    int days = context.pathTokens.asInt("days")
    def datastore = context.get(Datastore)

    context.blocking {
      datastore.deleteOlderThan(days)
    } then {
      context.render("$it records deleted")
    }
  }
}

// Or when using the handlers dsl
handlers {
  get("deleteOlderThan/:days") { Datastore datastore ->
    blocking {
      datastore.deleteOlderThan(pathTokens.asInt("days"))
    } then {
      context.render("$it records deleted")
    }
  }
}

The Callable submitted as the blocking operation is executed asynchronously (i.e. the blocking() method returns instantly), in a separate thread pool. The result that it returns will processed back on a request processing (i.e. compute) thread.

See the Context#blocking(Callable) method for more details.

7.3 Performing async operations

The Context#promise(Action<Fulfiller<T>>) for integrating with async APIs. It is essentially a mechanism for adapting 3rd party APIs to Ratpack’s promise type.

import ratpack.handling.*;
import ratpack.exec.Fulfiller;
import ratpack.func.Action;

public class PromiseUsingJavaHandler implements Handler {
  public void handle(final Context context) {
    context.promise(new Action<Fulfiller<String>>() {
      public void execute(final Fulfiller<String> fulfiller) {
        new Thread(new Runnable() {
          public void run() {
            fulfiller.success("hello world!");
          }
        }).start();
      }
    }).then(new Action<String>() {
      public void execute(String string) {
        context.render(string);
      }
    });
  }
}

class PromiseUsingGroovyHandler implements Handler {
  void handle(Context context) {
    context.promise { Fulfiller<String> fulfiller ->
      Thread.start {
        fulfiller.success("hello world!")
      }
    } then { String string ->
      context.render(string)
    }
  }
}

// Or when using the handlers dsl
handlers {
  get {
    promise { Fulfiller<String> fulfiller ->
      Thread.start {
        fulfiller.success("hello world!")
      }
    } then {
      render(it)
    }
  }
}

It is important to note that the promise is always fulfilled on a compute thread managed by Ratpack. When the “fulfiller” is invoked from a non Ratpack thread (perhaps it’s a thread managed by the 3rd party async API) the promise subscriber will be invoked on a Ratpack thread.

7.4 Async composition and avoiding callback hell

One of the challenges of asynchronous programming lies in composition. Non trivial asynchronous programming can quickly descend into a phenomenon known as “callback hell”, which is the term used to describe the incomprehensibility of many layers of nested callbacks.

Elegantly and cleanly composing async operations together into complex workflows is an area of rapid innovation at this time. Ratpack does not attempt to provide a framework for asynchronous composition. Instead, it aims to integrate and provide adapters to specialised tools for this task. An example of this approach is Ratpack’s integration with RxJava.

In general, integration is a matter of adapting Ratpack’s Promise type with the composition primitive of the target framework.

8 Google Guice integration

The ratpack-guice jar provides integration with Google Guice for modularisation and dependency injection. It is typically used in most Ratpack applications, but it is not a mandatory dependency. However, the ratpack-groovy jar does depend on the Guice integration.

See the Guice package API documentation for detailed usage information.

8.1 Modules

Guice provides the concept of a module, which is a kind of recipe for providing objects. See Guice’s “Getting Started” documentation for details.

The ratpack-guice library provides the ModuleRegistry type for registering modules to be used.

8.2 Dependency injected handlers

The Guice integration gives you a means of decoupling the components of your application. You can factor out functionality into standalone (i.e. non Handler) objects and use these objects from your handlers. This makes your code more maintainable and more testable. This is the standard “Dependency Injection” or “Inversion of Control” pattern.

The Guice class provides static handler() factory methods for creating root handlers that are the basis of the application. These methods (the commonly used ones) expose Chain instance that can be used to build the application’s handler chain. The instance exposed by these methods provide a registry (via the getRegistry()) method that can be used to construct dependency injected handler instances.

See the documentation of the Guice class for example code.

8.3 Modules as plugins

Ratpack does not have a formal plugin system. However, reusable functionality can be packaged as Guice modules.

For example, that ratpack-jackson library provides the JacksonModule class which is a Guice module. To integrate Jackson into your Guice backed Ratpack application (e.g. for serializing objects as JSON), you simply use this module.

In Groovy script application this is as easy as:

import ratpack.jackson.JacksonModule
import static ratpack.jackson.Jackson.json
import static ratpack.groovy.Groovy.ratpack

ratpack {
  modules {
    register new JacksonModule()
  }
  handlers {
    get("some-json") {
      render json(user: 1)  // will render '{user: 1}'
    }
  }
}

See the Guice package API documentation for more info on registering modules.

See the Jackson package API documentation for more info on using the Jackson integration.

8.4 Guice and the context registry

TODO guice backed registry impl

This offers an alternative to dependency injected handlers, as objects can just be retrieved on demand from the context.

More usefully, this means that Ratpack infrastructure can be integrated via Guice modules. For example, an implementation of the ServerErrorHandler can be provided by a Guice module. Because Guice bound objects are integrated into the context registry lookup mechanism, this implementation will participate in the error handling infrastructure.

This is true of all Ratpack infrastructure that works via context registry lookup, such as Renderer implementations for example.

9 Testing Ratpack applications

Ratpack provides testing support, both at the functional and unit level. The ratpack-test library contains the core support, and ratpack-test-groovy provides convenient Groovy extensions.

Note: The ratpack and ratpack-groovy Gradle plugins auto configure these libraries to be added to the test classpath.

9.1 Unit testing

The primary integration point between Ratpack and your code is the Handler contract. Ratpack provides the UnitTest.handle() and GroovyUnitTest.handle() for contriving the invocation of a handler. As the routing of requests is also implemented via handlers in Ratpack, this can also be used to test the routing.

Ratpack doesn’t couple to any particular test framework. It only provides some utilities that you can use from whatever test framework you choose. However, we strongly recommend using the Groovy based Spock Framework, even if your application is implemented in Java.

There is no special setup or teardown required for unit testing Ratpack applications, as by definition they require no server infrastructure.

9.2 Functional testing

Functional testing for Ratpack is built up around the ApplicationUnderTest.

This interface provides the address of the running application. Implementations of this interface will take care of starting the server for you.

9.2.1 TestHttpClient

Ratpack provides TestHttpClient in ratpack-test-groovy, this is a client that makes it very simple to test status codes and responses.

Note below we use @Delegate so we just need to call get() in the when block instead of client.get().


import ratpack.groovy.test.LocalScriptApplicationUnderTest import ratpack.groovy.test.TestHttpClient import ratpack.groovy.test.TestHttpClients import ratpack.test.ApplicationUnderTest class SiteSmokeSpec { LocalScriptApplicationUnderTest aut = new LocalScriptApplicationUnderTest() @Delegate TestHttpClient client = TestHttpClients.testHttpClient(aut) def "Check Site Index"() { get("index.html") assert response.statusCode == 200 assert response.body.asString().contains('<title>Ratpack: A toolkit for JVM web applications</title>') } def "Check Site Root"() { get() assert response.statusCode == 200 assert response.body.asString().contains('<title>Ratpack: A toolkit for JVM web applications</title>') } def cleanup() { aut.stop() } }

9.2.2 Geb

Geb can also be used we just need to set up the correct base URL and make sure the test app is shut down.

To set the correct base URL we will use the ServerBackedApplicationUnderTest instance to get the address and give that to the Geb browser instance.

For shutting down the app we will call stop in the cleanup function.

An example of a Geb based test is available here.

9.2.3 Application subsets, modules and extensions

The ratpack.test.embed package (provided by the ratpack-test library) provides the EmbeddedApplication interface and implementations. These can be used to functionally test features in isolation, by creating small Ratpack apps within test cases and is actually the basis for how Ratpack itself is tested. This approach is most commonly used for functionally testing reusable Ratpack components (e.g. Guice modules) but can also be used for functionally testing a subset of an application.

The LaunchConfigEmbeddedApplication is a convenient abstract super class, where implementors only need to provide a LaunchConfig to define the application to test. The ratpack.groovy.test.embed package (provided by the ratpack-groovy-test library) provides the ClosureBackedEmbeddedApplication implementation that uses user supplied closures as the basis of the application to test. This is the preferred implementation to use as it provides Guice support, flexibility and is easy to use.

The EmbeddedApplication type extends the ApplicationUnderTest type. This makes them convenient to use with the TestHttpClient mechanism provided by the ratpack-groovy-test library.

10 Groovy

Groovy is an alternative JVM programming language. It has a strong synergy with Java and many language and library features that make it a compelling programming environment. Ratpack provides strong integration with Groovy via the ratpack-groovy and ratpack-groovy-test libraries. Writing Ratpack applications in Groovy generally leads to less code through Groovy’s concise syntax compared to Java and a more productive and enjoyable development experience. To be clear though, Ratpack applications do not need to be written in Groovy.

Groovy is commonly known as a dynamic language. However, Groovy 2.0 added full static typing and static compilation as an option. Ratpack’s Groovy support is strictly designed to fully support “static Groovy” and also leverages the newest features of Groovy to avoid introducing boilerplate code to achieve this goal. Said another way, Ratpack’s Groovy support does not use any dynamic language features and has a strongly typed API.

TODO: find decent links describing static Groovy and use above

10.1 Prerequisites

If you are new to Groovy, you may want to research the following foundational Groovy topics before proceeding:

  1. Closures
  2. The def keyword

TODO: what else should be in this list? Also, links needed

something else

10.2 Ratpack Groovy API

TODO: explain that Groovy API wraps Java API and mirrors it in corresponding ratpack.groovy.*

10.2.1 @DelegatesTo

TODO: explain this Groovy feature and outline Ratpack’s extensive use of it

10.3 GroovyRatpackMain

The ratpack-groovy library provides the GroovyRatpackMain application entry point that bootstraps the Groovy support. This extends from RatpackMain, but forces the handler factory to be an instance of GroovyScriptFileHandlerFactory.

10.4 ratpack.groovy script

TODO: introduce DSL used in this file, discuss reloading when in reloadable mode

10.5 handlers {} DSL

TODO: introduce the GroovyChain DSL, and closures as handlers

10.6 GroovyChainAction

TODO: Point out the existence of GroovyChainAction

10.7 Testing

TODO: Discuss Groovy testing specifics (might move some content from the testing chapter)

11 RxJava

The excellent RxJava can be used in Ratpack applications to elegantly compose asynchronous operations.

The ratpack-rx JAR provides with RxRatpack class that provides static methods for adapting Ratpack promises to RxJava’s Observable.

The ratpack-rx module as of 0.9.4 is built against (and depends on) RxJava 0.18.1.

11.1 Initialization

The RxRatpack.initialize() must be called to fully enable the integration. This method only needs to be called once for the JVM’s lifetime.

11.2 Observing Ratpack

The integration is based on the RxRatpack.observe() and RxRatpack.observeEach() static methods. These methods adapt Ratpack’s promise type into an observable, which can then be used with all of the observable operators that RxJava offers.

For example, blocking operations can be easily observed.

import ratpack.handling.Handler;
import ratpack.handling.Context;
import ratpack.exec.Promise;
import java.util.concurrent.Callable;
import rx.functions.Func1;
import rx.functions.Action1;

import ratpack.test.UnitTest;
import ratpack.test.handling.HandlingResult;
import ratpack.func.Actions;

import static ratpack.rx.RxRatpack.observe;

public class Example {

  public static class ReactiveHandler implements Handler {
    public void handle(final Context context) {
      Promise<String> promise = context.blocking(new Callable<String>() {
        public String call() {
          return "hello world";
        }
      });

      observe(promise).map(new Func1<String, String>() {
        public String call(String input) {
          return input.toUpperCase();
        }
      }).subscribe(new Action1<String>() {
        public void call(String str) {
          context.render(str);
        }
      });
    }
  }

  public static void main(String[] args) {
    HandlingResult result = UnitTest.handle(new ReactiveHandler(), Actions.noop());
    assert result.rendered(String.class).equals("HELLO WORLD");
  }

}

A similar example in the Groovy DSL would look like:

import static ratpack.rx.RxRatpack.observe

handlers {
  handler {
    observe(blocking {
      // do some blocking IO
      "hello world"
    }) map { String input ->
      input.toUpperCase()
    } subscribe {
      render it // renders: HELLO WORLD
    }
  }
}

11.3 Implicit error handling

A key feature of the RxJava integration is the implicit error handling. All observable sequences have an implicit default error handling strategy of forwarding the exception to the execution context error handler. In practice, this means that error handlers rarely need to be defined for observable sequences.

import ratpack.handling.Handler;
import ratpack.handling.Context;
import ratpack.exec.Promise;
import rx.functions.Action1;
import java.util.concurrent.Callable;

import ratpack.test.UnitTest;
import ratpack.test.handling.HandlingResult;
import ratpack.func.Actions;

import static ratpack.rx.RxRatpack.observe;

import java.io.IOException;

public class Example {
  public static class ReactiveHandler implements Handler {
    public void handle(Context context) {
      Promise<String> promise = context.blocking(new Callable<String>() {
        public String call() throws Exception {
          throw new IOException("error!");
        }
      });

      observe(promise).subscribe(new Action1<String>() {
        public void call(String str) {
          // will never be called because of error
        }
      });
    }
  }

  public static void main(String[] args) {
    HandlingResult result = UnitTest.handle(new ReactiveHandler(), Actions.noop());
    assert result.getException().getMessage().equals("error!");
  }
}

In this case, the exception thrown during the blocking operation will be forwarded to the current ServerErrorHandler, which will probably render an error page to the response. If the subscriber does implement an error handling strategy, it will be used instead of the implicit error handler.

The implicit error handling applies to all observables that are created on Ratpack managed threads. It is not restricted to observables that are backed by Ratpack promises.

12 Coda Hale Metrics

The ratpack-codahale-metrics jar provides integration with Coda Hale’s Metrics library.

Coda Hale Metrics is one of the best metrics libraries out there for the JVM. It provides a toolkit of metric types and metric reporters that will give a deep insight into your application’s performance, whether that be at development time or real time in production. It allows you to easily capture statistics like the number of requests served or response times, and also more generic information like the state of your internal collections, queues or how many times some portion of code is being executed. By measuring your code you will know exactly what your code is doing when it runs and be able to make informed optimization decisions.

Ratpack’s integration with Coda Hale Metrics means that many of these key metrics are captured already for you, simply by registering the Guice module. Should you require further insight then Ratpack also makes it easy for you to capture additional metrics using the library’s many metric types and then report all of these metrics to your required output using the library’s metric reporters.

See CodaHaleMetricsModule for detailed usage information.

12.1 Built-in metrics

Ratpack provides built-in metric collectors for key metrics. When metrics are enabled within your application using CodaHaleMetricsModule.metrics(), the built-in metric collectors will automatically be enabled too.

Ratpack has built-in metric collectors for:

Ratpack also has support for Coda Hale Metric’s JVM instrumentation. See CodaHaleMetricsModule.jvmMetrics() for usage information.

12.2 Custom metrics

Ratpack enables you to capture your own application metrics in two ways:

  1. Obtaining the MetricRegistry via dependency injection or context registry lookup and registering your own metrics with it.
  2. Add metrics annotations to your Guice injected classes.

See CodaHaleMetricsModule.metrics() for more details.

12.3 Reporting metrics

Ratpack supports metric reporters for the following outputs:

For an example of how to consume real-time metrics with websockets, see the example-books project.

12.4 Health checks

Health checks verify that your application components or responsibilities are performing as expected.

For detailed information on how to create your own application health checks and how to run them, see CodaHaleMetricsModule.healthChecks().

13 Building with Gradle

The recommended way to build Ratpack applications is to use the Gradle Build System, by way of the Gradle plugins provided by the Ratpack project.

Ratpack is purely a runtime toolkit and not also a development time tool like Ruby on Rails and Grails. This means that you can use whatever you like to build a Ratpack app. The provided Gradle plugins merely provide convenience and are not fundamental to Ratpack development.

13.1 Setup

The first requirement is to apply the Gradle plugin to your Gradle project…

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath "io.ratpack:ratpack-gradle:0.9.4"
  }
}

apply plugin: "ratpack"

repositories {
  jcenter()
}

Or for a Groovy based Ratpack project…

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath "io.ratpack:ratpack-gradle:0.9.4"
  }
}

apply plugin: "ratpack-groovy"

repositories {
  jcenter()
}

The 'ratpack' plugin applies the core Gradle 'java' plugin. The 'ratpack-groovy' plugin applies the core Gradle 'groovy' plugin. This means that you can start adding code and dependencies to your app like a standard Gradle based project (e.g. putting source in src/main/[groovy|java]). Note that the 'ratpack-groovy' plugin implicitly applies the 'ratpack' plugin.

13.2 Ratpack dependencies

To depend on a Ratpack extension library, simply add it as a regular compile dependency…

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath "io.ratpack:ratpack-gradle:0.9.4"
  }
}

apply plugin: "ratpack-groovy"

repositories {
  jcenter()
}

dependencies {
  compile ratpack.dependency("jackson")
}

Using ratpack.dependency("jackson") is equivalent to "io.ratpack:ratpack-jackson:«version of ratpack-gradle dependency»". This is the recommended way to add dependencies that are part of the core distribution.

The 'ratpack' plugin adds the following implicit dependencies:

The 'ratpack-groovy' plugin adds the following implicit dependencies:

The available libraries can be browsed via Bintray. All Ratpack jars are published to both Bintray’s JCenter and Maven Central.

13.3 The ‘application’ plugin

Both the 'ratpack' and 'ratpack-groovy' plugins also apply the core Gradle 'application' plugin. This plugin provides the ability to create a standalone executable distribution of your software. This is the preferred deployment format for Ratpack applications.

The 'application' plugin requires the main class (i.e. entry point) of your application to be specified. This is preconfigured by the 'ratpack' and 'ratpack-groovy' plugins to be the RatpackMain and GroovyRatpackMain respectively. This can be changed if you wish to use a custom entry point (consult the 'application' plugin documentation).

13.4 The base dir

The src/ratpack directory in the Gradle project effectively becomes the base dir of your Ratpack application. That is, these are the files that are visible to your application (e.g. static files to serve).

This directory will be included in the distribution built by the 'application' plugin as the app directory. This directory will be added to the classpath when starting the application, and will also be the JVM working directory.

13.4.1 ratpack.properties and launch configuration

It is a good idea to immediately put a (potentially empty) ratpack.properties file in the src/ratpack directory. When the application is launched, this file contributes to the application LaunchConfig.

For example, to configure the maximum request size that the application will accept, add the following to the src/ratpack/ratpack.properties file…

ratpack.maxContentLength=1024

See Launching for more information about specifying the effective LaunchConfig for the application.

13.4.2 The ‘ratpack.groovy’ script

The 'ratpack-groovy' plugin expects the main application definition to be located at either src/ratpack/ratpack.groovy or src/ratpack/Ratpack.groovy. This file should not go in to src/main/groovy.

See Groovy for more information about the contents of this file.

13.4.3 Generated files

Your build may generate files to be served or otherwise used at runtime The best approach is to have the tasks that generate these files generate into a subdirectory of src/ratpack. The Ratpack Gradle plugins add a special task named 'prepareBaseDir’ that you should make depend on your generation task.

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath "io.ratpack:ratpack-gradle:0.9.4"
  }
}

apply plugin: "ratpack"

repositories {
  jcenter()
}

task generateDocs(type: Copy) {
  from "src/documentation"
  into "src/ratpack/documentation"
  expand version: project.version
}

prepareBaseDir {
  dependsOn generateDocs
}

// Ensure that 'clean' removes the files generated by 'generateDocs'
clean {
  delete generateDocs
}

Making 'prepareBaseDir' depend on your generation task ensures that it is invoked whenever the application is run or assembled.

13.5 Running the application

The 'application' plugin provides the 'run' task for starting the Ratpack application. This is a task of the core Gradle JavaExec type. The 'ratpack' plugin configures this 'run' task to start the process in src/ratpack and to launch with the system property 'ratpack.reloadable' set to true (which enables development time code reloading).

If you wish to set extra system properties for development time execution, you can configure this task…

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath "io.ratpack:ratpack-gradle:0.9.4"
  }
}

apply plugin: "ratpack"

repositories {
  jcenter()
}

run {
  systemProperty "ratpack.other.dbPassword", "secret"
}

13.6 Class reloading via SpringLoaded

With a little extra configuration, you can enable reloading of changed classes at development time without restarting the server. This is achieved by leveraging SpringLoaded, by Pivotal Labs. To use SpringLoaded in your Ratpack project, you need to add a dependency on the SpringLoaded agent.

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath "io.ratpack:ratpack-gradle:0.9.4"
  }
}

apply plugin: "ratpack"

repositories {
  jcenter()
  maven { url "http://repo.springsource.org/repo" } // for springloaded
}

dependencies {
  springloaded "org.springsource.loaded:springloaded:1.1.5.RELEASE"
}

Reloading is now enabled for your application. SpringLoaded will detect changed class files while your application is running and patch the code in memory.

An effective workflow is to open to terminal windows. In the first, execute…

./gradlew run

In the second, run the following after making a code change…

./gradlew classes

If you’d like to have Gradle automatically compile changes as they happen, you can use the Gradle Watch plugin.

Note: You do not need SpringLoaded support for reloading changes to the src/ratpack/Ratpack.groovy file when using 'ratpack-groovy', nor do you need to have Gradle recompile the code. The reloading of this file is handled at runtime in reloadable mode.

13.7 IntelliJ IDEA support

The 'ratpack' Gradle plugin integrates with the core 'idea' Gradle plugin. A “Run Configuration” is automatically created, making it easy to start your application from within IDEA. The run configuration mimics the configuration of the 'run' Gradle task, including integration with SpringLoaded.

To use the integration, you need to apply the 'idea' plugin to your build.

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath "io.ratpack:ratpack-gradle:0.9.4"
  }
}

apply plugin: "ratpack"
apply plugin: "idea"

repositories {
  jcenter()
}

You can now have the build generate metadata that allows the project to be opened in IDEA, by running…

./gradlew idea

This will generate a «project name».ipr file, which can be opened with IDEA. Once the project is opened, you will see a “Run Configuration” named “Ratpack Run” that can be used to start the application.

13.7.1 Reloading

If you have configured your build to use SpringLoaded, it will also be used by IDEA. However, IDEA will not automatically recompile code while there is an active run configuration. This means that after making a code change (to anything other than src/ratpack/Ratpack.groovy) you need to click “Make Project” in the “Build” menu (or use the corresponding key shortcut).

14 Deploying to Heroku

Heroku is a scalable polyglot cloud application platform. It allows you to focus on writing applications in the language of your choice, and then easily deploy them to the cloud without having to manually manage servers, load balancing, log aggregation, etc. Heroku does not have, nor does it need, any special Ratpack support above its generic support for JVM applications. Heroku is a rich platform, with many add-ons such as Postgres, Redis, Memcache, RabbitMQ, New Relic, etc. It is a compelling option for serving Ratpack applications.

Deployments to Heroku are typically in source form. Deploying is as simple as performing a Git push at the end of your CI pipeline. Many popular cloud CI tools such as drone.io and Travis-CI (among others) have convenient support for pushing to Heroku.

It is recommended to read the Heroku Quickstart and Buildpack documentation if you are new to Heroku. The rest of this chapter outlines the requirements and necessary configuration for deploying Ratpack applications to Heroku.

14.1 Gradle based builds

Ratpack applications can be built by any build system, but the Ratpack team recommends Gradle. Heroku has native support for Gradle via the Gradle buildpack, which works well with the Ratpack Gradle plugins.

All Gradle projects should use the Gradle Wrapper. If the wrapper scripts are present in your project, Heroku will detect that your project is built with Gradle.

14.1.1 Java Version

Heroku allows the required Java version to be set via system.properties file in the root of the source tree.

java.runtime.version=1.7

At the time of writing, this file is required as Heroku defaults to Java 6 and Ratpack requires Java 7.

14.1.2 Building

The Gradle buildpack will invoke ./gradlew stage. The Ratpack Gradle plugins do not add a stage task to your build, so you need to add it yourself and make it build your application. The simplest way to do this is make the stage task depend on the installApp task which is added by the Ratpack Gradle plugins.

A minimalistic build.gradle looks like this:

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath "io.ratpack:ratpack-gradle:0.9.4"
  }
}

apply plugin: "ratpack"

task stage {
  dependsOn installApp
}

This will build your application and install it into the directory build/install/«project name».

14.1.2.1 Setting the project name

By default, Gradle uses the project’s directory name as the project’s name. In Heroku (and some CI servers), the project is built in a temporary directory with a randomly assigned name. To ensure that the project uses a consistent name, add a declaration to settings.gradle in the root of your project:

rootProject.name = "«project name»"

This is a good practice for any Gradle project.

14.1.3 Running (Procfile)

The Procfile lives at the root of your application and specifies the command that Heroku should use to start your application. In this file, add a declaration that Heroku should start a web process by executing the launch script created by the build.

web: build/install/«project name»/bin/«project name»

14.1.4 Configuration

There are several ways to configure the environment for applications deployed to Heroku. You may want to use these mechanisms to set environment variables and/or JVM system properties to configure your application.

The application entry points that are used when using the ratpack and ratpack-groovy Gradle plugins support using JVM system properties to contribute to the LaunchConfig (see the launching chapter chapter for more detail). The starter scripts created by the Ratpack Gradle plugins, support the standard JAVA_OPTS environment variable and an app specific «PROJECT_NAME»_OPTS environment variable. If your application name was foo-Bar, then the environment variable would be named FOO_BAR_OPTS.

One way to bring this all together is to launch your application via env:

web: env "FOO_BAR_OPTS=-Dratpack.other.dbPassword=secret" build/install/«project name»/bin/«project name»

It is generally preferable to not use JAVA_OPTS as Heroku sets this to useful defaults for the platform.

Another approach is to use config vars. The benefit of setting the environment via the Procfile is that this information is in your versioned source tree. The benefit of using config vars is that they are only available to those with permissions to view/change them with Heroku. It is possible to combine both approaches by setting config vars for values that should be secret (like passwords) and referencing them in your Procfile.

web: env "FOO_BAR_OPTS=-Dratpack.other.dbPassword=$SECRET_DB_PASSWORD" build/install/«project name»/bin/«project name»

Now it is easy to see which properties and environment variables are set in the source tree, but sensitive values are only visible via the Heroku management tools.

14.2 Other build tools and binary deployments

The Ratpack project does not provide any “official” integration with other build tools. However, it is quite possible to use whatever tool you like to build a Ratpack application for Heroku or even to deploy in binary form.

Once you have a compiled Ratpack application in the Heroku environment (either through building with another build tool or by binary deployment), you can simply start the application by using java directly.

web: java ratpack.groovy.launch.GroovyRatpackMain

See the launching chapter chapter for more detail on starting Ratpack applications.

14.3 General Considerations

14.3.1 Port and public address

You may want to consider setting -Dratpack.publicAddress to the public name of your application so that application redirects work as expected. See redirect() for more details.

Heroku assigns each application an ephemeral port number, made available by the PORT environment variable. The RatpackMain entry point implicitly supports this environment variable if the ratpack.port system property is not set.

15 The Ratpack project

15.1 Credits

Ratpack has been made possible by the following people.

15.1.1 Active project members

The following people are currently actively contributing their time to Ratpack.

15.1.2 Contributors

The following people have provided significant contributions.

15.1.3 Past project members

The following people have contributed their time to Ratpack in the past, but are no longer actively working on it.

15.2 About this manual

15.2.1 Resources

15.2.1.1 Libraries

15.2.1.2 Fonts

Web fonts are served by Google Web Fonts.

15.2.1.3 Images