Chapter 1. Welcome to Ratpack

At its core, Ratpack is a high-performance framework that marries efficient processing with ease of use. In the modern state of web application development, developer productivity has a necessary emphasis in many web frameworks. Where Ratpack differentiates itself in this space is by continuing to focus on developer productivity, while also providing a foundation upon which performance and resource utilization are forefront considerations. Furthermore, the framework’s concise and easy-to-use application programming interface (API) allows applications to be designed in a way where they can be semantically reasoned about without disjointed concepts or complexities that plague other Java-based web frameworks. Even for a novice developer, Ratpack applications can be quickly built and understood without a high barrier of spool up time. In every respect, Ratpack aims to make it easy to build web applications on the Java Virtual Machine (JVM) that foster an environment of productivity, performance, and efficiency.

Ratpack takes a lightly opinionated approach to the manner in which applications are built—in other words, as much that can be facilitated in a “one-liner” as possible is presented as such, and when the complexity of a project’s implementation outgrows those facilities, the framework gets out of the way quickly. Ratpack’s infrastructure is designed to be extended, and follows sensible patterns for doing so. As your web application’s requirements grow, Ratpack will continue to be a powerful utility from which you can harness complex concepts—like reactive programming and deterministic asynchronous processing—in the way that most suitably fits your needs.

In the effort of supporting semantic APIs that are easy to use and understand, Ratpack employs many of the luxuries of Java that have more recently become available. Lambda expressions, functional programming interfaces, and method references are a few of the newer language features that the framework has come to adopt. Leveraging these aspects of the language allows the framework to guide applications toward the simplest path possible for solving the problem at hand.

Ratpack also provides out-of-the-box support for newer versions of the Apache Groovy programming language. As a long-time language on the JVM, Groovy brings a lot to the table when designing APIs that are semantically concise, including its robust and inherent capability to provide flexible, domain-specific languages (DSLs) as the layer upon which applications are built. In recent years, Groovy has established itself as much more than a simple dynamic alternative to core Java, and has incorporated features into the language that make it a premier choice when building any modern application on the JVM. Static compilation, for example, gives applications built with Groovy similar performance levels to those built with Java. Furthermore, the ability to inform Groovy’s compilation engine as to the structure of a DSL also serves as a benefit to using Groovy. The ability to coerce a closure to a single abstract method type is also utilized by Ratpack to work with closures as functional programming interfaces.

In supporting Groovy as a first-class language, Ratpack places the very best of the JVM’s ecosystem at your disposal. However, as you will find throughout this book, Ratpack’s integration with Groovy ends at reducing the visual verbosity of applications. As a capable dynamic language, Groovy opens the door for building systems that employ elaborate compile-time processing, code generation, and runtime method dispatching that can quickly become difficult to follow and debug. In Ratpack, you will find no “magic” that happens behind the scenes, and indeed the framework takes the approach of limiting complexity as much as possible, so that you always know what your application is doing.

Hello, World!

To better understand the nature of Ratpack, we can begin by taking a look at the simplest possible application type: a “Hello, World!” example. Here, we will start by showing how the Groovy integration can be leveraged to rapidly prototype applications and get a sense of their form and function. We can make use of Groovy’s ability to act as a scripting language to bring in the necessary framework dependencies, define a simple application structure, and run it from the command line. If we start by looking at the example outlined in Example 1-1, we see the entirety of what is required to get a Groovy-based Ratpack application up and running.

Example 1-1. “Hello, World!” application for a Groovy-based Ratpack implementation
@Grab('io.ratpack:ratpack-groovy:1.3.3') 1

import static ratpack.groovy.Groovy.ratpack 2

ratpack {
  handlers {
    get { 3
      render "Hello, World!" 4
    }
  }
}
1

We start our simple script by using Groovy’s built-in dependency management system, known as “Grapes,” to “grab” the necessary framework dependency and make it available to our runtime classpath.

2

Statically importing the Groovy.ratpack method provides our script with the DSL within which we define our application’s structure and features.

3

The handler chain will be covered in more depth in the next section; for now, all you need to know is that the get handler is binding a processing block for incoming HTTP GET requests (like those from a browser).

4

Within the application handler, we render a “Hello, World!” message back to the client.

This example represents all the code necessary to build your first Ratpack application! The designed simplicity makes it easy to follow and with just these few lines of code you are well on your way to becoming a proficient developer with Ratpack.

Running the Example

Groovy can run nearly everywhere that Java can, and if you do not already have the Groovy runtime at your disposal, you can download it from the project’s website. For Mac OS X, Linux, or Cygwin users, you can alternatively choose to use SDKMAN! to install Groovy. Windows users can use the PowerShell-based package manager Posh-GVM. However you obtain Groovy, ensure that the language binaries are available in your runtime PATH.

Once you have Groovy in place on your system, you can run the “Hello, World!” application from the command line. If you place the contents of the example into an app.groovy file, and use the groovy command-line utility, you will see your application start in your console. The output presented in Example 1-2 shows what you will see.

Example 1-2. “Hello, World!” application output
$ groovy app.groovy
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
WARNING: No slf4j logging binding found for Ratpack, there will be no logging output.
WARNING: Please add an slf4j binding, such as slf4j-log4j2, to the classpath.
WARNING: More info may be found here: http://ratpack.io/manual/current/logging.html
Ratpack started (development) for http://localhost:5050

The most notable part of the output is the last line, which Ratpack outputs to indicate where the running application can be accessed. The default HTTP bind port in Ratpack is 5050, so if you open a browser and navigate to http://localhost:5050, you will see the “Hello, World!” result.

Rapid Prototyping

In the interest of accommodating developer productivity, Ratpack applications can be quickly and easily modified, with changes reflected in real time. While you have the “Hello, World!” example application running, from another window you can open the app.groovy file and make changes to it. For example, if you change the text from “Hello, World!” to “Hello, Ratpack!” and refresh your browser, you will see the new message reflected. This core Ratpack concept allows developers to quickly test and vet changes or features without the need for a full application restart. This is undisputedly a powerful facility when getting the initial structure and features of an application layed out.

In the chapters that follow, we will tour the features of Ratpack that make it amenable to projects of all shapes and sizes. One such feature is its comprehensive integration with the Gradle build system, which makes it easy to get a full-featured project running and built. As will be demonstrated and discussed later, Ratpack’s integration with Gradle allows it to leverage the build system’s reloading capabilities that further the efforts of rapid productivity. All this is to say, the rapid prototyping capabilities offered by Ratpack extend beyond its simplest form, and are inherent at all levels of application complexity.

Arguably one of the most notable features of Ratpack—and one that you have undoubtedly already realized from running the “Hello, World!” application—is how quickly applications get up and running. Most applications sport sub-second startup times, which further benefits the process of rapid prototyping. When you make a significant number of complex changes or changes that otherwise require a full application restart, you are not left with a long process of waiting for an application container to get your code changes live, because startup times are short and quick. With developer productivity in mind, Ratpack is designed in a way that gets code started and running as fast as absolutely possible.

Handler Chain

The most important structure in the definition of a Ratpack application is undoubtedly the handler chain. If we dissect the code in Example 1-1, we see the handlers block, which is the area in which application request handlers are defined. The handler chain defines the edge of your application and the flow by which requests are processed. To draw a parallel to more traditional Java web development patterns, the concept of handlers in Ratpack are analogous to filters and servlets in servlet API terms. That is to say, one or more handlers can participate in the processing of a request and a handler is responsible for sending a response back.

Handlers are processed top-down, and can be defined to match on HTTP verb, requested URL, and other concepts that are provided by the chain API. As you explore the handler chain, you will find that, like the get method that was demonstrated in our example application, there are semantic methods for post, put, delete, patch, and options as well. Using these conveniently named binding methods, you can appropriate the request handling logic to the corresponding HTTP verb, making your application’s request handling easy to follow and quick to understand.

In our example application, within the handler chain we can see that we are binding a request handler for the HTTP GET verb on the default route. Ratpack will route HTTP GET requests for the / URI to this handler, which is evidenced by the browser test that we conducted earlier. Through the handler chain API, we can see that similar binding methods exist for all HTTP verbs. As you build out the specification for your application, you need only define the request handlers inline in the handler chain.

URL Path Bindings

Each of the methods on the handler chain API for working with the different HTTP verbs allow you to specify the path to which the handler should be bound. If we consider again the “Hello, World!” application from earlier, we can extend it slightly to include a second handler, which binds to the /foo endpoint. The code in Example 1-3 shows the addition of the second handler. As you explore this sample code, note that in Ratpack, you should not specify the leading / when defining URI paths.

Example 1-3. “Hello, World!” application with /foo handler
@Grab('io.ratpack:ratpack-groovy:1.3.3')

import static ratpack.groovy.Groovy.ratpack

ratpack {
  handlers {
    get {
      render "Hello, World!"
    }
    get("foo") { 1
      render "Hello, Foo!"
    }
  }
}
1

Here, we bind a request handler to /foo. Take note that we are able to leverage the fact that Groovy does not require us to wrap the final argument in the parens of a method call when that argument is a closure.

If we run this application and open a browser to http://localhost:5050/foo, we will find that we are greeted with the “Hello, Foo!” message, as we would expect. Applying handlers to URI routes in this way works in the same way as the corresponding methods of the various other HTTP verbs.

A special and important caveat to the handler chain is that only a single handler can be bound to a given URI. While it is said that requests flow through the handler chain, once a handler is found for a given URI path binding, no other handlers bound to that same path will be eligible for processing. This rule applies regardless of the HTTP verb to which multiple handlers are bound. In the case where you wish to have multiple handlers apply to one route (say, in a resource-oriented RESTful API), Ratpack provides a special mechanism for doing so.

There are two similar semantics for representing a handler type that are agnostic to a request’s HTTP verb: all and path. These two types do the same thing, with the difference being that the latter takes a URI path as an argument and the former does not. In the handler chain, these handler types qualify for processing of any incoming request, and by making use of this, we can bind a handler to a given URI and define its capability of processing any HTTP verb type. For the purposes of understanding multiverb bindings, we will expand our prior example to make the /foo endpoint handle post GET and POST requests. To do so, we will utilize the path chain method and within its handler we will introduce the byMethod mechanism. Example 1-4 shows our expanded sample code.

Example 1-4. The byMethod specification
@Grab('io.ratpack:ratpack-groovy:1.3.3')

import static ratpack.groovy.Groovy.ratpack

ratpack {
  handlers {
    get {
      render "Hello, World!"
    }
    path("foo") { 1
      byMethod { 2
        get { 3
          render "Hello, Foo Get!"
        }
        post { 4
          render "Hello, Foo Post!"
        }
      }
    }
  }
}
1

Here, we have changed the binding from get to path.

2

Within the handler logic, we use the byMethod, which is provided as part of the handler’s context (this will be covered in more depth later in the book).

3

The byMethod specification allows us to bind our verb-specific handlers. Here, we specify the handler for an HTTP GET request.

4

Similarly, we define a handler for POST requests.

If we run this application and open our browser again to http://localhost:5050/foo, we are greeted with the “Hello, Foo Get!” message. Now, if we open a command line and use the cURL utility, we can perform a POST request to our application, as follows:

curl -XPOST http://localhost:5050/foo

The output from that call will result in seeing the “Hello, Foo Post!” message.

Prefixed Routes

The handler chain is all about building a contextual definition of your application’s request-taking flow. As the edges of your application grow, so too does the complexity of understanding the various definitions. To that end, when you are building your application’s handler chain, you can choose to prefix a set of handlers under a given route. This becomes incredibly valuable in conversations about designing RESTful APIs, but for now, just remember that building a prefixed chain is a capability that allows you to logically organize your application better.

Prefixed routes are fairly self-explanatory in practice, but to further your understanding, consider a scenario where you are building an ecommerce application, and you wish to have a set of endpoints dedicated to working with products. For the sake of discussion, let’s suppose that we have a list endpoint, which lists all products; a get endpoint, for getting a specific product; and a search endpoint for looking up products. It would be repetitive to have to write each of these out as product/list, product/get, and product/search, and those definitions could get lost easily as the complexity of your application grows. Instead, we will use the chain API’s prefix method to wrap them all up in a subchain dedicated strictly for products. The code in Example 1-5 demonstrates using the prefix method.

Example 1-5. Using the prefix method
@Grab('io.ratpack:ratpack-groovy:1.3.3')

import static ratpack.groovy.Groovy.ratpack

ratpack {
  handlers {
    prefix("products") {
      get("list") {
        render "Product List"
      }
      get("get") {
        render "Product Get"
      }
      get("search") {
        render "Product Search"
      }
    }
  }
}

To the prefix method, we specify the prefixed URI pattern; the closure supplied to the prefix method created a subchain, which acts with the exact same behavior as the handler chain otherwise, with the handlers being bound within the /products route.

Prefixed routes can be described at any level within a chain, meaning that they can be further nested within other prefix subchains. This can allow you to build complex request processing flows that are easy to get a handle on without the verbosity of defining route depth at each handler definition.

Path Tokens

When binding to a path, Ratpack provides a mechanism by which tokens can be defined and later extracted by the handler. This allows variable data, such as an ID, to be accessed from the request path and handled accordingly.

Consider the code in Example 1-6, which demonstrates a path handler that uses the token notation.

Example 1-6. Path tokens
@Grab('io.ratpack:ratpack-groovy:1.3.3')

import static ratpack.groovy.Groovy.ratpack

ratpack {
  handlers {
    get("foo/:id?") { 1
      def name = pathTokens.id ?: "World" 2
      response.send "Hello $name!"
    }
  }
}
1

Path tokens are prefixed with a colon and are named. The ? at the end of the token indicates that this is an optional token. Without it, the id property will always be required.

2

Within the handler logic, we use the pathTokens.id call to get access to the id path token.

The pathTokens type is an implementation of a TypeCoercingMap, which provides you with some assistance in translating path tokens to respective types. By default, the value will come out a string, but using the coercion methods available will simplify your code. For example, if we wanted to work with the id field from the example as a Long type instead of as a string, we could change the code to: pathTokens.asLong('id'). Similar coercion methods are available for Boolean (asBool), Byte (asByte), Short (asShort), and Integer (asInt).

Request Parameters

Request parameters are made available to handlers through the request object. Unlike path tokens, request parameters are not defined in the path binding. Example 1-7 demonstrates a handler that extracts a request parameter and handles the request accordingly.

Example 1-7. Using request parameters
@Grab('io.ratpack:ratpack-groovy:1.3.3')
import static ratpack.groovy.Groovy.ratpack

ratpack {
  handlers {
    get {
      def name = request.queryParams.name ?: "Guest" 1
      response.send "Hello, $name!"
    }
  }
}
1

Off the request object, we can access the queryParams map, which holds keys for the specifically named query parameters.

Running this application and navigating to http://127.0.0.1:5050?name=John will render the message “Hello, John!” in your browser, as we would expect from the handler code. Also as we would expect, leaving off the ?name=John request parameter will yield the default “Hello, Guest!” message.

Request parameters will always be String types, so it is important to properly translate them to their corresponding type for use in external classes or services.

Parsing Request Data

Structured request data can be extracted from the request using the parse method in our handler. Through this interface, request data can be converted into a data structure for additional processing. Example 1-8 demonstrates this capability.

Example 1-8. Parsing request data
@Grab('io.ratpack:ratpack-groovy:1.3.3')

import static ratpack.groovy.Groovy.ratpack
import ratpack.form.Form

ratpack {
  handlers {
    all {
      byMethod {
        get { 1
          response.send "text/html", """\
            <!DOCTYPE html>
            <html>
            <body>
            <form method="POST">
            <div>
            <label for="checked">Check</label>
            <input type="checkbox" id="checked" name="checked">
            </div>
            <div>
            <label for="name">Name</label>
            <input type="text" id="name" name="name">
            </div>
            <div>
            <input type="submit">
            </div>
            </form>
            </body>
            </html>
          """.stripIndent()
        }
        post {
          parse(Form).then { formData -> 2
            def msg = formData.checked ? "Thanks for the check!" :
              "Why didn't you check??"
            response.send "text/html", """\
            <!DOCTYPE html>
            <html>
            <body>
            <h1>Welcome, ${formData.name ?: 'Guest'}!</h1>
            <span>${msg}</span>
            """.stripIndent()
          }
        }
      }
    }
  }
}
1

This handler, while verbose, is fairly simple. All we are doing here is serving up an HTML form to work with in a simple view. This is not best practice, and normally we would want this served from the project’s assets. Serving content is covered later in the book, so this is here only for demonstration’s sake.

2

Within the chain’s post handler, we call the parse method and inform it that we want a Form object returned. The resulting formData gives us access to the data that was submitted from the get handler’s HTML form. We can work with this like any other map.

This example is serving HTML directly out of the handlers, so pointing a web browser to the application URL will this time show us a proper HTML form. Toggling the form’s checkbox and submitting will show the different behavior of the POST handler, which converts the form data into a Form object before making a decision about what to render.

Content Negotiation in Handlers

We previously covered how to build method-agnostic handlers that have specialized logic for processing different HTTP requests for the same URI binding. You will recall that the mechanism by which this is accomplished is known as the byMethod specification. In addition to specifying handler logic for different method types, Ratpack provides a specification with which handlers can apply specific logic based on the request’s content type.

Content type negotiation comes into play when designing request handlers that are responsible for sending data back to a client in a specified format. For example, your application may design a handler that renders an HTML page when the request specifies that it desires a text/html content type. That same handler may instead render a model as JSON data when a requested content type of application/json is specified. For managing this within your handler logic, Ratpack provides what is known as the byContent specification, and works similarly to the byMethod specification.

To illustrate this capability better, consider an application where we have a request handler bound to the /users endpoint. When a consumer of our application opens the endpoint in a browser, we want it to render back a list of User objects; when a client library accesses the endpoint and requests JSON or XML data, we want the data serialized as such. The byContent specification gives us flexibility to adapt the handler’s response according to what the consumer is capable of receiving. The code in Example 1-9 demonstrates how this application would look.

Example 1-9. The byContent specification
@Grab('io.ratpack:ratpack-groovy:1.3.3')

import static ratpack.groovy.Groovy.ratpack
import static groovy.json.JsonOutput.toJson

class User { 1
  String username
  String email
}

def user1 = new User( 2
  username: "ratpack",
  email: "ratpack@ratpack.io"
)
def user2 = new User(
  username: "danveloper",
  email: "danielpwoods@gmail.com"
)

def users = [user1, user2] 3

ratpack {
  handlers {
    get("users") {
      byContent { 4
        html { 5
          def usersHtml = users.collect { user ->
            """\
            |<div>
            |<b>Username:</b> ${user.username}
            |<b>Email:</b> ${user.email}
            |</div>
            """.stripMargin()
          }.join()

          render """\
          |<!DOCTYPE html>
          |<html>
          |<head>
          |<title>User List</title>
          |</head>
          |<body>
          |<h1>Users</h1>
          |${usersHtml}
          |</body>
          |</html>
          """.stripMargin()
        }
        json { 6
          render toJson(users)
        }
        xml { 7
          def xmlStrings = users.collect { user ->
            """
            <user>
              <username>${user.username}</username>
              <email>${user.email}</email>
            </user>
            """.toString()
          }.join()
          render "<users>${xmlStrings}</users>"
        }
      }
    }
  }
}
1

We will start by envisioning a simple User model object with some simple properties.

2

For demonstrative purposes, we will bootstrap a couple of test objects.

3

To keep things simple, we can maintain a simple global list of our bootstrapped users. (Note that this is just for the purposes of demonstrating the example. Proper data-driven web applications will be covered in depth later in the book.)

4

Within our handler, we can access the byContent method, which gives us some convenience methods similar to how the handler chain works. Within the closure we provide, we can specify handlers for the different content types our application is capable of providing.

5

The html convenience method provides the ability to perform processing specifically when a text/html content type is requested.

6

Similarly, json allows you to specify logic for an application/json content type. Note that here we are using Groovy’s JsonOutput class to assist in serializing the user list to JSON. Ratpack has extensive support for JSON rendering that will be covered later in the book.

7

Ratpack even provides convenience methods for working with XML content types. If your users request application/xml, this logic will be activated to respond to them.

If you run this script and navigate to http://localhost:5050/users, your browser will pull up a web page prominently displaying the list of users, as shown in Figure 1-1.

lrpk 0101
Figure 1-1. The byContent spec (HTML output)

Next, we can test JSON serialization. By again using the cURL command-line utility, we can issue a request that specifies that we want application/json content. Note that content type is specified through the request’s Accept header. In cURL, we specify this header by supplying the -H "Accept: application/json" argument. The output in Example 1-10 shows running the request and the response from our application.

Example 1-10. The byContent spec (JSON output)
$ curl -H "Accept: application/json" localhost:5050/users
[{"username":"ratpack","email":"ratpack@ratpack.io"},{"username":"danveloper", ↵
"email":"danielpwoods@gmail.com"}]

Great! As you can see, the appropriate logic blocks provided to the byContent method are being activated accordingly. To be sure, we can run a similar test, shown in Example 1-11, this time checking that application/xml is working properly.

Example 1-11. The byContent spec (XML output)
$ curl -H "Accept: application/xml" localhost:5050/users
<users><user><username>ratpack</username><email>ratpack@ratpack.io</email></user> ↵
<user><username>danveloper</username><email>danielpwoods@gmail.com</email></user> ↵
</users>

Everything is working exactly as we would expect. It is important to know how byContent operates: when no content type is specified, the first handler is always activated. In our example, if we run the cURL command again, this time without specifying the header, we are returned HTML. As an exercise, if you move the json block before html and rerun the test, you indeed will see JSON output.

Using byContent also gives you the ability to specify custom content types. If your application requires rendering content in a specialized way according to a consumer’s capabilities, you can build processing into your handler using the type method on byContent. If we change our demonstration slightly to also include a handler for the application/vnd.app.custom+json content type, we can reimagine a shortened version of our handler, such as that shown in Example 1-12.

Example 1-12. Custom content type
get("users") {
  byContent {
    html {
      // ... snipped for brevity ...
    }
    json {
      // ... snipped for brevity ...
    }
    xml {
      // ... snipped for brevity ...
    }
    type("application/vnd.app.custom+json") { 1
      render toJson([
        some_custom_data: "my custom data",
        type: "custom-users",
        users: users
      ])
    }
  }
}
1

Here, we apply a logic block to an application-specific content type. Within it, we render back a customized data structure with properties that consumers will expect.

If we run the cURL test again, this time specifying application/vnd.app.custom+json, we will see our custom data structure rendered, as shown in Example 1-13.

Example 1-13. The byContent spec (custom type output)
$ curl -H "Accept: application/vnd.app.custom+json" localhost:5050/users
{"some_custom_data":"my custom data","type":"custom-users", "users": ↵
[{"username":"ratpack","email":"ratpack@ratpack.io"},{"username":"danveloper", ↵
"email":"danielpwoods@gmail.com"}]}

This is exactly the output you would expect. This capability makes Ratpack a powerful choice for building applications where the content type serves as a way to drive the form of the models you render back to your consumers. But, what about when a consumer requests a content type that does not match anything we have built into the byContent block? For that, we need to leverage another feature of the specification, which is specifically designed for this. The noMatch method allows us to attach processing in this case. The shortened example shown in Example 1-14 demonstrates applying the noMatch block to your byMethod logic.

Example 1-14. The byContent spec (no match handling)
get("users") {
  byContent {
    html {
      // ... snipped for brevity ...
    }
    json {
      // ... snipped for brevity ...
    }
    xml {
      // ... snipped for brevity ...
    }
    type("application/vnd.app.custom+json") {
      // ... snipped for brevity ...
    }
    noMatch { 1
      response.status 400
      render "negotiation not possible."
    }
  }
}
1

Within the noMatch block, we will set the response status to 400 (Bad Request) and send back a plain-text message. Consumers will recognize the HTTP error code and realize there is a problem with the content type.

If we run the cURL test again, this time with an arbitrary content type string, we will see a “negotiation not possible” message returned. The output in Example 1-15 shows the output for this test.

Example 1-15. The byContent spec (no match output)
$ curl -H "Accept: application/nothing" localhost:5050/users
negotiation not possible.

You will recall that when no content type was specified, we defaulted to the first defined block (html); however, that may not be the desired behavior when an unknown content type is specified. We can further this example by saying that our application’s requirements are that when no content type is matched, we want to render the JSON explicitly. To facilitate this, instead of specifying a closure to the noMatch method, we can provide a string with the content type to which the request processing should be routed. In Example 1-16, we specify that when no match is found, we instead want to treat the request as though it were application/json.

Example 1-16. The byContent spec (no match to JSON translation)
get("users") {
  byContent {
    html {
      // ... snipped for brevity ...
    }
    json {
      // ... snipped for brevity ...
    }
    xml {
      // ... snipped for brevity ...
    }
    type("application/vnd.app.custom+json") {
      // ... snipped for brevity ...
    }
    noMatch "application/json"
  }
}

Running the test again with the application/nothing content type, we will notice that this time we are met with the same output that we saw earlier in our JSON test. Giving your application the ability to direct processing according to a request’s acceptable content type makes it easy to build powerful APIs and multiuse handlers in a concise way. Your understanding of content type negotation in Ratpack will serve as an excellent foundation for the examples throughout this book. When we get into discussions of building APIs and data-driven applications, content type negotiation becomes a paramount consideration.

Chapter Summary

In this introductory chapter, we have opened the door on your understanding of how to build web applications with Ratpack. From covering the basic “Hello, World!” demonstration at the beginning, to working through a basic understanding of the handler chain, and how to work with requests, you now have the necessary exposure to get off the ground with Ratpack. This chapter’s content will serve as a building block for the text to come. The concepts demonstrated here will prove ever-useful as we expand the conversation, and introduce you to the more advanced and robust concepts and features that Ratpack provides.

Get Learning Ratpack now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.