TinyHttpHandler.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package net.morimekta.tiny.server.http;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.TreeSet;

/**
 * Class simplifying the HttpHandler in a similar fashion to the jakarta
 * <code>HttpServlet</code> class. Just implement the matching method to
 * the HTTP method you need served.
 */
public abstract class TinyHttpHandler implements HttpHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(TinyHttpHandler.class);

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        try {
            exchange.getResponseHeaders().set("Server", "tiny-server");
            switch (exchange.getRequestMethod()) {
                case "GET":
                    doGet(exchange);
                    break;
                case "POST":
                    doPost(exchange);
                    break;
                case "HEAD":
                    doHead(exchange);
                    break;
                case "OPTIONS":
                    doOptions(exchange);
                    break;
                case "TRACE":
                    doTrace(exchange);
                    break;
                case "PUT":
                    doPut(exchange);
                    break;
                case "DELETE":
                    doDelete(exchange);
                    break;
                default: {
                    LOGGER.warn("Unknown request method: {} {} HTTP/1.1",
                                exchange.getRequestMethod(),
                                exchange.getRequestURI().getPath());
                    exchange.sendResponseHeaders(TinyHttpStatus.SC_METHOD_NOT_ALLOWED, 0);
                }
            }
        } catch (Exception e) {
            if (exchange.getResponseCode() <= 0) {
                LOGGER.warn("Exception handling: {} {} HTTP/1.1",
                            exchange.getRequestMethod(),
                            exchange.getRequestURI().getPath(),
                            e);
                exchange.sendResponseHeaders(TinyHttpStatus.SC_INTERNAL, 0);
            } else {
                LOGGER.error("Exception handling: {} {} HTTP/1.1",
                             exchange.getRequestMethod(),
                             exchange.getRequestURI().getPath(),
                             e);
            }
        } finally {
            exchange.close();
        }
    }

    /**
     * Handle a GET request.
     *
     * @param exchange The HTTP exchange.
     * @throws IOException If failed to handle the request.
     */
    protected void doGet(HttpExchange exchange) throws IOException {
        exchange.sendResponseHeaders(TinyHttpStatus.SC_METHOD_NOT_ALLOWED, 0);
    }

    /**
     * Handle a POST request.
     *
     * @param exchange The HTTP exchange.
     * @throws IOException If failed to handle the request.
     */
    protected void doPost(HttpExchange exchange) throws IOException {
        exchange.sendResponseHeaders(TinyHttpStatus.SC_METHOD_NOT_ALLOWED, 0);
    }

    /**
     * Handle a HEAD request.
     *
     * @param exchange The HTTP exchange.
     * @throws IOException If failed to handle the request.
     */
    protected void doHead(HttpExchange exchange) throws IOException {
        exchange.sendResponseHeaders(TinyHttpStatus.SC_METHOD_NOT_ALLOWED, -1);
    }

    /**
     * Handle an OPTIONS request.
     *
     * @param exchange The HTTP exchange.
     * @throws IOException If failed to handle the request.
     */
    protected void doOptions(HttpExchange exchange) throws IOException {
        Set<String> methods = new TreeSet<>();
        Set<String> corsMethods = new TreeSet<>();
        for (Method method : getClass().getDeclaredMethods()) {
            switch (method.getName()) {
                case "doGet":
                    methods.add("GET");
                    corsMethods.add("GET");
                    break;
                case "doPost":
                    methods.add("POST");
                    break;
                case "doHead":
                    methods.add("HEAD");
                    break;
                case "doTrace":
                    methods.add("TRACE");
                    break;
                case "doPut":
                    methods.add("PUT");
                    break;
                case "doDelete":
                    methods.add("DELETE");
                    break;
                default:
                    break;
            }
        }
        if (!methods.isEmpty()) {
            exchange.getResponseHeaders().set("Allow", String.join(", ", methods));
        }
        if (!corsMethods.isEmpty()) {
            exchange.getResponseHeaders().set("Access-Control-Allow-Methods", String.join(", ", corsMethods));
        }
        if (methods.isEmpty() && corsMethods.isEmpty()) {
            // ... nothing implemented?
            exchange.sendResponseHeaders(TinyHttpStatus.SC_INTERNAL, 0);
        } else {
            exchange.sendResponseHeaders(TinyHttpStatus.SC_NO_CONTENT, -1);
        }
    }

    /**
     * Handle a TRACE request.
     *
     * @param exchange The HTTP exchange.
     * @throws IOException If failed to handle the request.
     */
    protected void doTrace(HttpExchange exchange) throws IOException {
        exchange.sendResponseHeaders(TinyHttpStatus.SC_METHOD_NOT_ALLOWED, 0);
    }

    /**
     * Handle a PUT request.
     *
     * @param exchange The HTTP exchange.
     * @throws IOException If failed to handle the request.
     */
    protected void doPut(HttpExchange exchange) throws IOException {
        exchange.sendResponseHeaders(TinyHttpStatus.SC_METHOD_NOT_ALLOWED, 0);
    }

    /**
     * Handle a DELETE request.
     *
     * @param exchange The HTTP exchange.
     * @throws IOException If failed to handle the request.
     */
    protected void doDelete(HttpExchange exchange) throws IOException {
        exchange.sendResponseHeaders(TinyHttpStatus.SC_METHOD_NOT_ALLOWED, 0);
    }
}