Request and Response model

Whether accessing querystring parameters, form data or headers, a number of convenient methods exist to get the values, or return defaults, or convert to numbers or some other types.

Contents:

Query Strings

These can all be accessed from the Request object by calling request.query() which returns a RequestParameters object.

public class QueryStringExampleHandler implements RouteHandler {
    public void handle(MuRequest request, MuResponse response, Map<String,String> pathParams) {

        // Returns null if there is no parameter with that value
        String something = request.query().get("something");

        // Specifying a default value:
        String somethingElse = request.query().get("something", "default value");

        // Getting a list, e.g. for ?something=value1&something=value2
        List<String> somethingList = request.query().getAll("something");

        // Gets a number, or returns the default value if it's missing or not a number.
        // There are also getFloat, getDouble, getLong and getBoolean methods
        int intValue = request.query().getInt("something", 42);

        response.sendChunk(something + " / " + somethingElse + " / " + somethingList + " / " + intValue);

        // You can loop through all query string values
        for (Map.Entry<String, List<String>> entry : request.query().all().entrySet()) {
            response.sendChunk('\n' + entry.getKey() + "=" + entry.getValue());
        }
    }
}
(see full file)

Try it: Run the above code in your browser.

Form data

Calling request.form() returns a RequestParameters object so all values are the same as for query strings:

public class FormDataExampleHandler implements RouteHandler {
    public void handle(MuRequest request, MuResponse response, Map<String,String> pathParams) throws IOException {

        // Returns null if there is no parameter with that value
        String something = request.form().get("something");

        // Specifying a default value:
        String somethingElse = request.form().get("something", "default value");

        // Gets a number, or returns the default value if it's missing or not a number.
        // There are also getFloat, getDouble, getLong and getBoolean methods
        int intValue = request.form().getInt("numberValue", 42);

        boolean checkboxValue = request.form().getBoolean("checkboxValue");

        response.sendChunk(something + "\n" + somethingElse + "\n" + intValue + "\n" + checkboxValue);

        // You can loop through all the form data
        response.sendChunk("\n\nLooping through all form values:");
        for (Map.Entry<String, List<String>> entry : request.form().all().entrySet()) {
            response.sendChunk('\n' + entry.getKey() + "=" + entry.getValue());
        }
    }
}
(see full file)

Try it: post some data to the server:

Uploads

Accessing the fields in multi-part form data is as above. To access uploaded files, see the Uploads documentation.

Headers

Request headers share most of the same getters as the query string and form data, however more options are available, including the ability to parse certain complex headers. See the Headers javadoc for more details.

public class HeaderExampleHandler implements RouteHandler {
    public void handle(MuRequest request, MuResponse response, Map<String,String> pathParams) {

        // Sending headers on the response:

        // Set a response header value:
        response.headers().set("X-Example-Time", new Date().toString());

        // Or add multiple values:
        response.headers().add("X-List", "Xavier");
        response.headers().add("X-List", "Wolf guy");


        // Getting headers from the request:

        // Returns null if there is no parameter with that value
        String userAgent = request.headers().get("User-Agent");

        // Specifying a default value:
        String defaultValue = request.headers().get("something", "default value");

        // Getting a list where a header has been added multiple times
        List<String> somethingList = request.headers().getAll("something");

        // Gets a number, or returns the default value if it's missing or not a number.
        int intValue = request.headers().getInt("something", 42);

        response.sendChunk(userAgent + "\n" + defaultValue + "\n" + somethingList + "\n" + intValue + "\n\n");

        // Parsers exist for some header values
        List<ParameterizedHeaderWithValue> accepts = request.headers().accept();
        for (ParameterizedHeaderWithValue accept : accepts) {
            String q = accept.parameter("q", "1.0");
            response.sendChunk("Accept " + accept.value() + " with q=" + q + "\n");
        }

        List<ParameterizedHeaderWithValue> acceptLanguages = request.headers().acceptLanguage();
        for (ParameterizedHeaderWithValue acceptLanguage : acceptLanguages) {
            String q = acceptLanguage.parameter("q", "1.0");
            response.sendChunk("Accept language: " + acceptLanguage.value() + " with q=" + q + "\n");
        }

        MediaType mediaType = request.headers().contentType();
        response.sendChunk("The content type of the request body is " + mediaType + "\n");

        ParameterizedHeader cacheControl = request.headers().cacheControl();
        response.sendChunk("Cache control: " + cacheControl.parameters() + "\n");

        List<ForwardedHeader> forwarded = request.headers().forwarded();
        response.sendChunk("Forwarded headers: " + ForwardedHeader.toString(forwarded) + "\n");

        // Or just loop through everything
        response.sendChunk("\n\nLooping through all headers:\n");
        for (Map.Entry<String, String> entry : request.headers()) {
            response.sendChunk(entry.getKey() + "=" + entry.getValue() + "\n");
        }
    }
}
(see full file)

Try it: Run the above code in your browser.

Note that the HeaderNames and HeaderValues classes contain a number of useful constants.

Cookies

Use the CookieBuilder class to create a new cookie and send it to the client with response.addCookie(Cookie).

Cookies can be read with request.cookie(String name) which returns an Optional<String> with the cookie value as the value if present:

public class CookieExampleHandler implements RouteHandler {
    public void handle(MuRequest request, MuResponse response, Map<String,String> pathParams) {

        switch (request.query().get("action", "view")) {
            case "view":

                Optional<String> cookie = request.cookie("mu-example-cookie");
                String cookieValue = cookie.isPresent() ? cookie.get() : "(no cookie)";
                response.write("Cookie value from request is " + cookieValue);

                break;
            case "create":

                Cookie newCookie = CookieBuilder.newCookie()
                    .withName("mu-example-cookie")
                    .withValue("CookieValue" + System.currentTimeMillis())
                    .withMaxAgeInSeconds(45)
                    .withPath("/model") // only send over /model/*
                    .secure(false) // only send over HTTPS
                    .httpOnly(true) // disable JavaScript access
                    .withSameSite("Lax") // Strict, Lax, or None
                    .build();

                response.addCookie(newCookie);
                response.write("Added cookie: " + newCookie);

                break;
            case "delete":

                // The name, path, and domain values must match the created-cookie values
                Cookie toDelete = CookieBuilder.newCookie()
                    .withName("mu-example-cookie")
                    .withValue("")
                    .withMaxAgeInSeconds(0)
                    .withPath("/model")
                    .build();

                response.addCookie(toDelete);
                response.write("Deleted cookie " + toDelete);

                break;
        }

    }
}
(see full file)

Try it: Create a cookie and view the cookie and then delete the cookie.

When creating cookies, it is recommended to set SameSite to Strict, and enable Secure and HTTPOnly to prevent a number of common attacks. The CookieBuilder's newSecureCookie() method creates a build with these values set.

It's important to note that cookie names and values can only use a limited subset of ASCII characters, so you cannot send arbitrary data (such as JSON) as a cookie value without encoding it first. One option to allow arbitrary data is to URL Encode the value. The withUrlEncodedValue method can be used in place of the withValue method to achieve this (note the consumer of the cookie needs to URL decode the value when accessing it.)

If a max age is not set, then the cookie will be a "session" cookie which means browsers may keep the cookie until the user closes their browser (or not, depending on the browser). To create a persistent cookie, use the withMaxAgeInSeconds with a positive number.

Matrix Parameters

URL paths can have attributes associated with each segment of the path. For example, in the following URL, there are 3 path segments (search, hotels, and suites). The hotels segment has 2 attributes: rating which has two values (4 and 5) and hasPool, whereas the suites segment just has a single attribute.

/search/hotels;rating=5;rating=4;hasPool=true/suites;beds=2

The following example shows how to locate a segment and extract the parameters for that segment:

public class MatrixParameterExampleHandler implements RouteHandler {
    public void handle(MuRequest request, MuResponse response, Map<String, String> pathParams) {
        PathMatch matcher = UriPattern.uriTemplateToRegex("/search/{type}/{subtype}").matcher(request.uri());
        if (matcher.fullyMatches()) {
            PathSegment typeSegment = matcher.segments().get("type");
            response.sendChunk("Search type=" + typeSegment.getPath());
            for (String rating : typeSegment.getMatrixParameters().get("rating")) {
                response.sendChunk("; rating=" + rating);
            }
            String hasPool = typeSegment.getMatrixParameters().getFirst("hasPool");
            response.sendChunk("; hasPool=" + hasPool + "\n\n");

            PathSegment subTypeSegment = matcher.segments().get("subtype");
            String beds = subTypeSegment.getMatrixParameters().getFirst("beds");
            response.sendChunk("Subtype=" + subTypeSegment.getPath() + " with beds=" + beds);
        } else {
            response.write("URL didn't match");
        }
    }
}
(see full file)

Request bodies

While form data and uploads can be accessed via the request object, there are other ways to read request bodies.

Calling request.readBodyAsString() returns the request body as a string. This is convenient if you are expecting a small text upload, however for binary or large uploads it is better to stream the data.

The inputStream() method on the request object is an Optional value which has a readable input stream if the client sent a body:

Optional<InputStream> inputStreamOption = request.inputStream();
if (inputStreamOption.isPresent()) {
    InputStream inputStream = inputStreamOption.get();
    // read the stream
}

Request bodies can also be read asynchronously.

The content type of the request data can be found by calling request.headers().contentType() which returns a MediaType object which has getType() and getSubtype() methods.

Responses

The MuResponse javadocs have detailed information on handling responses. Generally speaking though, a status, content-type, and headers can be set, followed by the response body.

The status can be set with response.status(int) which defaults to 200 if not set. The content type can be set with response.contentType(String) which defaults to text/plain if text is sent; otherwise application/octet-stream is used. Note that the ContentTypes class defines constants for many content types.

See the sections above for setting headers and cookies.

Response bodies

There are several options for sending response bodies:

Writing a single string as a full response

This is a good option if you have some text to send and you don't require streaming:

response.write("Hello, world");

This will send a fixed-length response to the client. Note you can only call write once per response, so if you find you want to call it multiple times, use one of the following options.

Sending multiple strings, chunk by chunk

The sendChunk(String) method will immediately send the text to the client and can be called multiple times:

public class SendChunkExample {
    public static void main(String[] args) {
        MuServer server = MuServerBuilder.httpServer()
            .addHandler(Method.GET, "/", (request, response, path) -> {

                response.contentType("text/html;charset=utf-8");
                response.sendChunk("Will send some chunks...<br>");
                for (int i = 0; i < 10; i++) {
                    response.sendChunk("Chunk " + i + "<br>");
                    Thread.sleep(500);
                }
                response.sendChunk("<br>Response complete.<br>");

            })
            .start();
        System.out.println("See some chunks at " + server.uri());
    }
}
(see full file)

Writing text to a PrintWriter

The response.writer() method returns a buffered UTF-8 java.io.PrintWriter. You can optionally call the flush() method on the writer to immediately send the contents.

public class ResponseTextWriterExample {
    public static void main(String[] args) {
        MuServer server = MuServerBuilder.httpServer()
            .addHandler(Method.GET, "/", (request, response, path) -> {

                response.contentType("text/html;charset=utf-8");
                try (PrintWriter writer = response.writer()) {
                    writer.append("Will send some messages...<br>");
                    for (int i = 0; i < 10; i++) {
                        writer.append("Sending message " + i + "<br>");
                        writer.flush();
                        Thread.sleep(500);
                    }
                    writer.append("<br>Response complete.<br>");
                }

            })
            .start();
        System.out.println("See some streaming at " + server.uri());
    }
}
(see full file)

Writing binary data to an output stream

To sent raw bytes to the client, response.outputStream() can be used which is a buffered OutputStream. Similar to the PrintStream, calling flush() is optional and will immediately send the buffered contents to the client.

In this example, an InputStream is found which is a PNG on the classpath, and as the data from the image is read it is written to the response output stream:

public class ResponseOutputStreamExample {
    public static void main(String[] args) {
        MuServer server = MuServerBuilder.httpServer()
            .addHandler(Method.GET, "/logo.png", (request, response, path) -> {

                response.contentType(ContentTypes.IMAGE_PNG);
                try (
                    InputStream inputStream = ResponseOutputStreamExample.class
                        .getResourceAsStream("/web/logos/android-chrome-512x512.png");
                    OutputStream responseStream = response.outputStream()) {

                    byte[] buffer = new byte[8192];
                    int read;
                    while ((read = inputStream.read(buffer)) > -1) {
                        responseStream.write(buffer, 0, read);
                    }

                }

            })
            .start();
        System.out.println("Download the logo at " + server.uri().resolve("/logo.png"));
    }
}
(see full file)

The PrintStream and OutputStream options are often useful if needing to integrate with other libraries that expect these types of objects.

Note: you should generally use a built-in static resource handler if you want to serve files from the filesystem or the classpath.

Writing byte-buffers asynchronously

When a request is being handled asynchronously, an AsyncHandle is returned which has non-blocking methods for writing ByteBuffer objects. This can be a good choice when responses may be long running as it will reduce the number of threads use, which is what the built-in static file handler does.

See the async handling documentation for more information.