Handling Requests

So far, you know how to configure a minimal HttpMaid instance, run it with an endpoint and understand routing. In this chapter, we will take a closer look at basic request handling and how to access fine-grained http features like header values, etc.

The HttpHandler interface

Let’s get back to the quickstart example configuration:

final HttpMaid httpMaid = anHttpMaid()
        .get("/hello", (request, response) -> response.setBody("Hi."))
        .build();
final PureJavaEndpoint endpoint = pureJavaEndpointFor(httpMaid).listeningOnThePort(1337);

We can take the (request, response) -> response.setBody("hi!") lambda and transform it into an actual class:

public final class HelloHandler implements HttpHandler {

    @Override
    public void handle(final HttpRequest request, final HttpResponse response) {
        response.setBody("hi!");
    }
}

As you can see, the lambda implements the HttpHandler interface, which provides the handle() method. In the handle() method, you get two objects as parameters: an HttpRequest and an HttpResponse. The HttpRequest object contains all data that was received in the request, and the HttpResponse object respectively holds all data that will be sent back as a response. Let’s take a deeper look at both of them.

The HttpRequest object

A HttpRequest instance holds all data that was sent in a request. The following paragraphs will walk you through each possible aspect and explain how to access it.

Request Method

The chapter Routing explained what the request method is. It can be queried from the current request object with the method() method:

final HttpMaid httpMaid = anHttpMaid()
        .get("/", (request, response) -> {
            final HttpRequestMethod method = request.method();
            System.out.println("method = " + method);
        })
        .build();

Request Route and Path Parameters

Every http request has a specific route. You can query that route of the current request using the path() method:

final HttpMaid httpMaid = anHttpMaid()
        .get("/*", (request, response) -> {
            final Path path = request.path();
            System.out.println("path = " + path);
        })
        .build();

As stated before, retrieving the path for https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol would result in /wiki/Hypertext_Transfer_Protocol

If you have specified any path parameters in the HttpMaid configuration, you can access the values of these parameters via the pathParameters() method. Assume this example configuration:

final HttpMaid httpMaid = anHttpMaid()
        .get("/hello/<name>", (request, response) -> {
            final PathParameters pathParameters = request.pathParameters();
            final String name = pathParameters.getPathParameter("name");
            response.setBody("hi " + name + "!");
        })
        .build();

When deployed the same way as the other examples (do not forget the PureJavaEndpoint), a request to http://localhost:1337/hello/bob would yield the string hi bob!

Query Parameters

In the http proctocol, you can append parameters to the route of a request. When you see a request like http://example.org/?mode=fullscreen, everything after the question mark are query parameters (in this case one single query parameter with the name mode and the value fullscreen).

Query parameters can be easily accessed from the HttpRequest object using the request.queryParameters() method. If you start this example configuration:

final HttpMaid httpMaid = anHttpMaid()
        .get("/hello", (request, response) -> {
            final QueryParameters queryParameters = request.queryParameters();
            final String name = queryParameters.parameter("name");
            response.setBody("hi " + name + "!");
        })
        .build();

and access http://localhost:1337/hello/?name=bob, you should again see the message hi bob!.

Request Headers

Every http request contains a set of headers, which are key-value pairs of strings. You can access the headers of a request using the headers() method:

final HttpMaid httpMaid = anHttpMaid()
        .get("/hello", (request, response) -> {
            final Headers headers = request.headers();
            final String name = headers.header("name");
            response.setBody("hi " + name + "!");
        })
        .build();

Unfortunately, it is not possible to set custom headers with requests in a generic web browser. Nonetheless, we can issue a request with the command line tool curl to try out the example:

$ curl --header "name: bob" http://localhost:1337/hello

This call should result in the response hi bob!.

Request Body

Some requests carry bodies, i.e. an additional chunk of data. You can conveniently access this body as a String using the bodyString() method. Note that if you want to receive a body, you cannot reliably use the GET request method - recommended and common is the use of POST or - less common - PUT.

final HttpMaid httpMaid = anHttpMaid()
        .post("hello", (request, response) -> {
            final String name = request.bodyString();
            response.setBody("hi " + name + "!");
        })
        .build();

Again, setting request bodies is not possible with generic web browsers, so we once again fall back to curl to try out the example:

$ curl --data "bob" http://localhost:1337/hello

As always, the output should be hi bob!.

The HttpResponse object

You can specify the response details using the HttpResponse object. The following paragraphs will walk you through the basic options (some more specialized features will be explained in later chapters).

Response Body

Every http response can carry a body. You already saw how to set the response body in the quickstart example - using the setBody() method. You can provide a String to this method and HttpMaid will send the body accordingly.

final HttpMaid httpMaid = anHttpMaid()
        .get("/test", (request, response) -> response.setBody("this is the body"))
        .build();

Go to http://localhost:1337/test and see the this is the body response.

You can also give the setBody() method an InputStream as parameter. HttpMaid will read the complete stream and send it as the body on the fly. This way, you can send large bodies without the need to allocate a huge String object.

Status Code

Every http response contains a so-called status code. These codes are standardized and tell the client whether the request could be handled successfully and what went wrong in case of an error. A successful request will be answered with the status code 200 (“OK”). Typical status codes for errors lie in the range between 400-500. The most famous 404 (“Not Found”) code indicates that the resource was not found (or that you are not allowed to see its existence). HttpMaid will set the status code by default to 200 (“OK”). If you want to set it manually, you can do this using the setStatus() method:

final HttpMaid httpMaid = anHttpMaid()
        .get("/test", (request, response) -> response.setStatus(201))
        .build();

Since browsers normally don’t show response status codes, we will fall back to curl again to try out the example:

$ curl -v http://localhost:1337/test

The output should look vaguely like this, with the < HTTP/1.1 201 Created line being of interest here:

*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 1337 (#0)
> GET /test HTTP/1.1
> Host: localhost:1337
> User-Agent: curl/7.47.0
> Accept: */*
> 
< HTTP/1.1 201 Created
< Date: Thu, 31 Oct 2020 16:36:24 GMT
< Transfer-encoding: chunked
< 
* Connection #0 to host localhost left intact

Response Headers

The same way as a request, a response can have headers. You can set individual headers using the addHeader() method:

final HttpMaid httpMaid = anHttpMaid()
        .get("/test", (request, response) -> response.addHeader("name", "Bob"))
        .build();

You can try it out with this curl command:

$ curl -v http://localhost:1337/test

and see an output like this, with the < Name: Bob line being of interest:

*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 1337 (#0)
> GET /test HTTP/1.1
> Host: localhost:1337
> User-Agent: curl/7.47.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Thu, 31 Oct 2020 16:43:53 GMT
< Name: Bob
< Transfer-encoding: chunked
< 
* Connection #0 to host localhost left intact

Since the Content-Type header is very prominent and commonly used, there also exists the setContentType() method to explicitly set this header.