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.

5 Handlers

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

5.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 Example 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.

5.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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PrintThenNextHandler implements Handler {
  private final String message;
  private final static Logger LOGGER = LoggerFactory.getLogger(PrintThenNextHandler.class);

  
  public PrintThenNextHandler(String message) {
    this.message = message;
  } 
  
  public void handle(Context context) {
    LOGGER.info(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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PrintThenInsertOrNextHandler implements Handler {
  private final String message;
  private final Handler[] handlers;
  private final static Logger LOGGER = LoggerFactory.getLogger(PrintThenInsertOrNextHandler.class);

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

  public void handle(Context context) {
    LOGGER.info(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.

5.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.HandlerFactory;
import ratpack.registry.Registry;

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(Registry registry) {
    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.launch.HandlerFactory;
import ratpack.registry.Registry;

import static ratpack.handling.Handlers.chain;

public class Application implements HandlerFactory {
  public Handler create(Registry registry) throws Exception {
    return chain(registry, (chain) -> chain
        .prefix("api", (api) -> api
          .delete("someResource", context -> {
            // delete the resource
          }))

        .assets("public")

        .get("foo/bar", ctx -> {
          // do stuff
        })
    );
  }
}

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.HandlerFactory
import ratpack.registry.Registry

import static ratpack.groovy.Groovy.chain

class Application implements HandlerFactory {
  Handler create(Registry registry) {
    chain(registry) {
      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.