/* * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.net.httpserver; import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.function.Predicate; /** * Implementations of {@link com.sun.net.httpserver.HttpHandler HttpHandler} * that implement various useful handlers, such as a static response handler, * or a conditional handler that complements one handler with another. * *

The factory method {@link #of(int, Headers, String)} provides a * means to create handlers with pre-set static response state. For example, a * {@code jsonHandler} that always returns 200 with the same json: *

{@code
 *    HttpHandlers.of(200,
 *                    Headers.of("Content-Type", "application/json"),
 *                    Files.readString(Path.of("some.json")));
 * }
* or a {@code notAllowedHandler} that always replies with 405 - * Method Not Allowed, and indicates the set of methods that are allowed: *
{@code
 *    HttpHandlers.of(405, Headers.of("Allow", "GET"), "");
 * }
* *

The functionality of a handler can be extended or enhanced through the * use of {@link #handleOrElse(Predicate, HttpHandler, HttpHandler) handleOrElse}, * which allows to complement a given handler. For example, complementing a * {@code jsonHandler} with notAllowedHandler: * *

{@code
 *    Predicate IS_GET = r -> r.getRequestMethod().equals("GET");
 *    var handler = HttpHandlers.handleOrElse(IS_GET, jsonHandler, notAllowedHandler);
 * }
* * The above handleOrElse {@code handler} offers an if-else like construct; * if the request method is "GET" then handling of the exchange is delegated to * the {@code jsonHandler}, otherwise handling of the exchange is delegated to * the {@code notAllowedHandler}. * * @since 18 */ public final class HttpHandlers { private HttpHandlers() { } /** * Complements a conditional {@code HttpHandler} with another handler. * *

This method creates a handleOrElse handler; an if-else like * construct. Exchanges who's request matches the {@code handlerTest} * predicate are handled by the {@code handler}. All remaining exchanges * are handled by the {@code fallbackHandler}. * *

Example of a nested handleOrElse handler: *

{@code
     *    Predicate IS_GET = r -> r.getRequestMethod().equals("GET");
     *    Predicate WANTS_DIGEST =  r -> r.getRequestHeaders().containsKey("Want-Digest");
     *
     *    var h1 = new SomeHandler();
     *    var h2 = HttpHandlers.handleOrElse(IS_GET, new SomeGetHandler(), h1);
     *    var h3 = HttpHandlers.handleOrElse(WANTS_DIGEST.and(IS_GET), new SomeDigestHandler(), h2);
     * }
* The {@code h3} handleOrElse handler delegates handling of the exchange to * {@code SomeDigestHandler} if the "Want-Digest" request header is present * and the request method is {@code GET}, otherwise it delegates handling of * the exchange to the {@code h2} handler. The {@code h2} handleOrElse * handler, in turn, delegates handling of the exchange to {@code * SomeGetHandler} if the request method is {@code GET}, otherwise it * delegates handling of the exchange to the {@code h1} handler. The {@code * h1} handler handles all exchanges that are not previously delegated to * either {@code SomeGetHandler} or {@code SomeDigestHandler}. * * @param handlerTest a request predicate * @param handler a conditional handler * @param fallbackHandler a fallback handler * @return a handler * @throws NullPointerException if any argument is null */ public static HttpHandler handleOrElse(Predicate handlerTest, HttpHandler handler, HttpHandler fallbackHandler) { Objects.requireNonNull(handlerTest); Objects.requireNonNull(handler); Objects.requireNonNull(fallbackHandler); return exchange -> { if (handlerTest.test(exchange)) handler.handle(exchange); else fallbackHandler.handle(exchange); }; } /** * Returns an {@code HttpHandler} that sends a response comprising the given * {@code statusCode}, {@code headers}, and {@code body}. * *

This method creates a handler that reads and discards the request * body before it sets the response state and sends the response. * *

{@code headers} are the effective headers of the response. The * response body bytes are a {@code UTF-8} encoded byte sequence of * {@code body}. The response headers * {@linkplain HttpExchange#sendResponseHeaders(int, long) are sent} with * the given {@code statusCode} and the body bytes' length (or {@code -1} * if the body is empty). The body bytes are then sent as response body, * unless the body is empty, in which case no response body is sent. * * @param statusCode a response status code * @param headers a headers * @param body a response body string * @return a handler * @throws IllegalArgumentException if statusCode is not a positive 3-digit * integer, as per rfc2616, section 6.1.1 * @throws NullPointerException if headers or body are null */ public static HttpHandler of(int statusCode, Headers headers, String body) { if (statusCode < 100 || statusCode > 999) throw new IllegalArgumentException("statusCode must be 3-digit: " + statusCode); Objects.requireNonNull(headers); Objects.requireNonNull(body); final var headersCopy = Headers.of(headers); final var bytes = body.getBytes(StandardCharsets.UTF_8); return exchange -> { try (exchange) { exchange.getRequestBody().readAllBytes(); exchange.getResponseHeaders().putAll(headersCopy); if (exchange.getRequestMethod().equals("HEAD")) { exchange.getResponseHeaders().set("Content-Length", Integer.toString(bytes.length)); exchange.sendResponseHeaders(statusCode, -1); } else if (bytes.length == 0) { exchange.sendResponseHeaders(statusCode, -1); } else { exchange.sendResponseHeaders(statusCode, bytes.length); exchange.getResponseBody().write(bytes); } } }; } }