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 Response.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 Renderer
s 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());
});
}
}
TODO introduce Jackson module and methods to help render arbitrary types to json
7.3.4 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.5 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.
Cookies
As with HTTP headers, cookies are available for inspection from an inbound request as they are for manipulation for an outbound response.
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());
});
}
}
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