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.

7 Basic HTTP

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

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

7.2 Reading the request

The body of the request is available via Request.getBody(). Alternatively, you can use the parser mechanism to turn the request body into an object representation. The context object provides the parse() method (and some variants).

Parsing works by selecting a Parser implementation from context registry. See parse()

7.2.1 JSON

Support for dealing with JSON request bodies is provided out of the box, based on Jackson. See Jackson parsing for examples.

7.2.2 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) {
    Promise<Form> form = context.parse(Form.class);

    form.then(f -> {
      // 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.

7.3 Sending a response

Sending an HTTP response in Ratpack is easy, efficient, and flexible. Like most things in Ratpack, transmitting a response to a client is done in a non-blocking manner. Ratpack provides a few mechanisms for sending a response. The methods exposed to manipulating the response can be found in the Response and Context objects.

7.3.1 Setting the response status

Setting the status of a response is as easy as calling Response#status(int) or Response#status(ratpack.http.Status).

import ratpack.http.Response;
import ratpack.http.Status;
import ratpack.test.embed.EmbeddedApp;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class Example {
  public static void main(String... args) throws Exception {
    EmbeddedApp.fromHandlers(chain -> chain
      .all(ctx -> ctx.getResponse().status(202).send("foo"))
    )
    .test(httpClient ->
      assertEquals(202, httpClient.get().getStatusCode())
    );
  }
}

7.3.2 Sending the response

There are a few ways to send a response body to the client.

The shortest way to send a response is to simply call Response#send(). This will send a response with no response body.

import ratpack.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;

import static org.junit.Assert.assertEquals;

public class Example {
  public static void main(String... args) throws Exception {
    EmbeddedApp
      .fromHandler(ctx -> ctx.getResponse().send())
      .test(httpClient -> {
        ReceivedResponse response = httpClient.get();
        assertEquals("", response.getBody().getText());
      });
  }
}

If you want to send a plain text response you can use Response#send(String).

import ratpack.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class Example {
  public static void main(String... args) throws Exception {
    EmbeddedApp
      .fromHandler(ctx -> ctx.getResponse().send("Ratpack is rad"))
      .test(httpClient -> {
        ReceivedResponse response = httpClient.get();
        assertTrue(response.getHeaders().get("Content-type").startsWith("text/plain;"));
        assertEquals("Ratpack is rad", response.getBody().getText());
      });
  }
}

There are additional send() methods that allow you send different the response body payloads, i.e. String, byte[], ByteBuf, as well as set the Content-type header. See Response for more on sending a response.

7.3.3 An alternative approach with Renderers

Sending empty or simple text responses may be fine but you may find yourself wanting to send a more complex response to the client. The Renderer is a mechanism that is able to render a given type to the client. More specifically, it’s the underlying mechanism that powers the render(Object) method, which can be found on the context object.

In the following example, we utilize the context’s render(Object) method to render an object of type String.

import ratpack.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;

import static org.junit.Assert.assertEquals;

public class Example {
  public static void main(String... args) throws Exception {
    EmbeddedApp
      .fromHandler(ctx -> ctx.render("Sent using render(Object)!"))
      .test(httpClient -> {
        ReceivedResponse response = httpClient.get();
        assertEquals("Sent using render(Object)!", response.getBody().getText());
      });
  }
}

Because the String is of type CharSequence, Ratpack finds and uses the CharSequenceRenderer to render the String. Where did this CharSequenceRenderer come from? Ratpack provides a number of Renderers out of the box, including but not limited to: CharSequenceRenderer, RenderableRenderer, PromiseRenderer, DefaultFileRenderer.

If you attempt to render a type that is not registered, it will result in a server error.

import ratpack.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;

import static org.junit.Assert.assertEquals;

public class Example {

  static class Foo {
    public String value;
  }

  public static void main(String... args) throws Exception {
    EmbeddedApp
      .fromHandler(ctx -> {
        Foo foo = new Foo();
        foo.value = "bar";
        ctx.render(foo);
      })
      .test(httpClient -> {
        ReceivedResponse response = httpClient.get();
        assertEquals(500, response.getStatusCode());
      });
  }
}

If you’d like to implement your own Renderer, Ratpack provides a RendererSupport that makes it easy to implement your own. You must also remember to register your Renderer so that Ratpack can use it.

import ratpack.handling.Context;
import ratpack.registry.Registry;
import ratpack.http.client.ReceivedResponse;
import ratpack.render.RendererSupport;
import ratpack.test.embed.EmbeddedApp;

import static org.junit.Assert.assertEquals;

public class Example {

  static class Foo {
    public String value;
  }

  static class FooRenderer extends RendererSupport<Foo> {
    @Override
    public void render(Context ctx, Foo foo) throws Exception {
      ctx.getResponse().send("Custom type: Foo, value=" + foo.value);
    }
  }

  public static void main(String... args) throws Exception {
    EmbeddedApp
      .fromHandlers(chain -> chain
        .register(Registry.single(new FooRenderer()))
        .all(ctx -> {
          Foo foo = new Foo();
          foo.value = "bar";
          ctx.render(foo);
        })
      )
      .test(httpClient -> {
        ReceivedResponse response = httpClient.get();
        assertEquals(200, response.getStatusCode());
        assertEquals("Custom type: Foo, value=bar", response.getBody().getText());
      });
  }
}

7.3.4 Sending JSON

Support for rendering arbitrary objects as JSON is based on Jackson. See Jackson rendering for examples.

7.3.5 Sending files

Sending static resources such as files can be done with sendFile(Path)

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

TODO introduce assets method

7.3.6 Before send

TODO introduce beforeSend method and the Response interface.

7.4 Headers

HTTP Header information is available from an incoming request as it is for an outgoing response.

7.4.1 Request headers

The Headers interface allows you to retrieve header information associated with the incoming request.

import ratpack.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;
import ratpack.http.Headers;

import static org.junit.Assert.assertEquals;

public class Example {
  public static void main(String... args) throws Exception {
    EmbeddedApp
      .fromHandler(ctx -> {
        Headers headers = ctx.getRequest().getHeaders();
        String clientHeader = headers.get("Client-Header");
        ctx.getResponse().send(clientHeader);
      })
      .test(httpClient -> {
        ReceivedResponse receivedResponse = httpClient
          .requestSpec(requestSpec ->
              requestSpec.getHeaders().set("Client-Header", "From Client")
          ).get();

        assertEquals("From Client", receivedResponse.getBody().getText());
      });
  }
}

7.4.2 Response headers

The MutableHeaders provides functionality that enables you to manipulate response headers via the response object Response#getHeaders().

import ratpack.http.MutableHeaders;
import ratpack.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;

import static org.junit.Assert.assertEquals;

public class Example {
  public static void main(String... args) throws Exception {
    EmbeddedApp
      .fromHandler(ctx -> {
        MutableHeaders headers = ctx.getResponse().getHeaders();
        headers.add("Custom-Header", "custom-header-value");
        ctx.getResponse().send("ok");
      })
      .test(httpClient -> {
        ReceivedResponse receivedResponse = httpClient.get();
        assertEquals("custom-header-value", receivedResponse.getHeaders().get("Custom-Header"));
      });
  }
}

Additionally you can set(CharSequence, Object), remove(CharSequence), clear() and more.

See MutableHeaders for more methods.

7.5 Cookies

As with HTTP headers, cookies are available for inspection from an inbound request as they are for manipulation for an outbound response.

7.5.1 Cookies from an inbound request

To retrieve the value of a cookie, you can use Request#oneCookie(String).

import ratpack.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;

import static org.junit.Assert.assertEquals;

public class Example {
  public static void main(String... args) throws Exception {
    EmbeddedApp.fromHandler(ctx -> {
      String username = ctx.getRequest().oneCookie("username");
      ctx.getResponse().send("Welcome to Ratpack, " + username + "!");
    }).test(httpClient -> {
      ReceivedResponse response = httpClient
        .requestSpec(requestSpec -> requestSpec
          .getHeaders()
          .set("Cookie", "username=hbogart1"))
        .get();

      assertEquals("Welcome to Ratpack, hbogart1!", response.getBody().getText());
    });
  }
}

You can also retrieve a set of cookies via Request#getCookies().

import io.netty.handler.codec.http.cookie.Cookie;
import ratpack.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;

import java.util.Set;

import static org.junit.Assert.assertEquals;

public class Example {
  public static void main(String... args) throws Exception {
    EmbeddedApp.fromHandler(ctx -> {
      Set<Cookie> cookies = ctx.getRequest().getCookies();
      assertEquals(1, cookies.size());
      Cookie cookie = cookies.iterator().next();
      assertEquals("username", cookie.name());
      assertEquals("hbogart1", cookie.value());
      ctx.getResponse().send("Welcome to Ratpack, " + cookie.value() + "!");
    }).test(httpClient -> {
      ReceivedResponse response = httpClient
        .requestSpec(requestSpec -> requestSpec
          .getHeaders()
          .set("Cookie", "username=hbogart1"))
        .get();

      assertEquals("Welcome to Ratpack, hbogart1!", response.getBody().getText());
    });
  }
}

7.5.2 Setting cookies for an outbound response

You can set cookies to be sent with the response Response#cookie(String, String). To retrieve the set of cookies to be set with the response you may use Response#getCookies().

import ratpack.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class Example {
  public static void main(String... args) throws Exception {
    EmbeddedApp.fromHandler(ctx -> {
      assertTrue(ctx.getResponse().getCookies().isEmpty());
      ctx.getResponse().cookie("whiskey", "make-it-rye");
      assertEquals(1, ctx.getResponse().getCookies().size());
      ctx.getResponse().send("ok");
    }).test(httpClient -> {
      ReceivedResponse response = httpClient.get();
      assertEquals("whiskey=make-it-rye", response.getHeaders().get("Set-Cookie"));
    });
  }
}

If you want to expire a cookie, you can do so with Response#expireCooke().

import ratpack.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;

import static org.junit.Assert.assertTrue;

public class Example {
  public static void main(String... args) throws Exception {
    EmbeddedApp.fromHandler(ctx -> {
      ctx.getResponse().expireCookie("username");
      ctx.getResponse().send("ok");
    }).test(httpClient -> {
      ReceivedResponse response = httpClient
        .requestSpec(requestSpec -> requestSpec
            .getHeaders().set("Cookie", "username=lbacall1")
        )
        .get();

      String setCookie = response.getHeaders().get("Set-Cookie");
      assertTrue(setCookie.startsWith("username=; Max-Age=0"));
    });
  }
}

7.6 Sessions

TODO introduce ratpack-sessions library