| /* | |
| * Copyright (c) 2021, 2024, 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.io.IOException; | |
| import java.io.OutputStream; | |
| import java.io.UncheckedIOException; | |
| import java.net.InetSocketAddress; | |
| import java.net.URLConnection; | |
| import java.nio.file.Path; | |
| import java.util.Objects; | |
| import java.util.function.Consumer; | |
| import java.util.function.Predicate; | |
| import java.util.function.UnaryOperator; | |
| import sun.net.httpserver.simpleserver.FileServerHandler; | |
| import sun.net.httpserver.simpleserver.OutputFilter; | |
| /** | |
| * A simple HTTP file server and its components (intended for testing, | |
| * development and debugging purposes only). | |
| * | |
| * <p> A simple file server is composed of three components: | |
| * <ul> | |
| * <li> an {@link HttpServer HttpServer} that is bound to a given address, </li> | |
| * <li> an {@link HttpHandler HttpHandler} that serves files from a given | |
| * directory path, and </li> | |
| * <li> an optional {@link Filter Filter} that prints log messages relating to | |
| * the exchanges handled by the server. </li> | |
| * </ul> | |
| * The individual server components can be retrieved for reuse and extension via | |
| * the static methods provided. | |
| * | |
| * <h2>Simple file server</h2> | |
| * | |
| * <p> The {@link #createFileServer(InetSocketAddress,Path,OutputLevel) createFileServer} | |
| * static factory method returns an {@link HttpServer HttpServer} that is a | |
| * simple out-of-the-box file server. The server comes with an initial handler | |
| * that serves files from a given directory path (and its subdirectories). | |
| * The output level determines what log messages are printed to | |
| * {@code System.out}, if any. | |
| * | |
| * <p> Example of a simple file server: | |
| * <pre>{@code | |
| * var addr = new InetSocketAddress(8080); | |
| * var server = SimpleFileServer.createFileServer(addr, Path.of("/some/path"), OutputLevel.INFO); | |
| * server.start(); | |
| * }</pre> | |
| * | |
| * <h2>File handler</h2> | |
| * | |
| * <p> The {@link #createFileHandler(Path) createFileHandler} static factory | |
| * method returns an {@code HttpHandler} that serves files and directory | |
| * listings. The handler supports only the <i>HEAD</i> and <i>GET</i> request | |
| * methods; to handle other request methods, one can either add additional | |
| * handlers to the server, or complement the file handler by composing a single | |
| * handler via | |
| * {@link HttpHandlers#handleOrElse(Predicate, HttpHandler, HttpHandler)}. | |
| * | |
| * <p>Example of composing a single handler: | |
| * <pre>{@code | |
| * var handler = HttpHandlers.handleOrElse( | |
| * (req) -> req.getRequestMethod().equals("PUT"), | |
| * (exchange) -> { | |
| * // validate and handle PUT request | |
| * }, | |
| * SimpleFileServer.createFileHandler(Path.of("/some/path"))) | |
| * ); | |
| * }</pre> | |
| * | |
| * <h2>Output filter</h2> | |
| * | |
| * <p> The {@link #createOutputFilter(OutputStream, OutputLevel) createOutputFilter} | |
| * static factory method returns a | |
| * {@link Filter#afterHandler(String, Consumer) post-processing filter} that | |
| * prints log messages relating to the exchanges handled by the server. The | |
| * output format is specified by the {@link OutputLevel outputLevel}. | |
| * | |
| * <p> Example of an output filter: | |
| * <pre>{@code | |
| * var filter = SimpleFileServer.createOutputFilter(System.out, OutputLevel.VERBOSE); | |
| * var server = HttpServer.create(new InetSocketAddress(8080), 10, "/some/path/", new SomeHandler(), filter); | |
| * server.start(); | |
| * }</pre> | |
| * | |
| * <h2>jwebserver Tool</h2> | |
| * | |
| * <p>A simple HTTP file server implementation is provided via the | |
| * {@code jwebserver} tool. | |
| * | |
| * @toolGuide jwebserver | |
| * | |
| * @since 18 | |
| */ | |
| public final class SimpleFileServer { | |
| private static final UnaryOperator<String> MIME_TABLE = | |
| URLConnection.getFileNameMap()::getContentTypeFor; | |
| private SimpleFileServer() { } | |
| /** | |
| * Describes the log message output level produced by the server when | |
| * processing exchanges. | |
| * | |
| * @since 18 | |
| */ | |
| public enum OutputLevel { | |
| /** | |
| * Used to specify no log message output level. | |
| */ | |
| NONE, | |
| /** | |
| * Used to specify the informative log message output level. | |
| * | |
| * <p> The log message format is based on the | |
| * <a href='https://www.w3.org/Daemon/User/Config/Logging.html#common-logfile-format'>Common Logfile Format</a>, | |
| * that includes the following information about an {@code HttpExchange}: | |
| * | |
| * <p> {@code remotehost rfc931 authuser [date] "request" status bytes} | |
| * | |
| * <p> Example: | |
| * <pre>{@code | |
| * 127.0.0.1 - - [22/Jun/2000:13:55:36 -0700] "GET /example.txt HTTP/1.1" 200 - | |
| * }</pre> | |
| * | |
| * @implNote The fields {@code rfc931}, {@code authuser} and {@code bytes} | |
| * are not captured in the implementation, so are always represented as | |
| * {@code '-'}. | |
| */ | |
| INFO, | |
| /** | |
| * Used to specify the verbose log message output level. | |
| * | |
| * <p> Additional to the information provided by the | |
| * {@linkplain OutputLevel#INFO info} level, the verbose level | |
| * includes the request and response headers of the {@code HttpExchange} | |
| * and the absolute path of the resource served up. | |
| */ | |
| VERBOSE | |
| } | |
| /** | |
| * Creates a <i>file server</i> that serves files from a given path. | |
| * | |
| * <p> The server is configured with an initial context that maps the | |
| * URI {@code path} to a <i>file handler</i>. The <i>file handler</i> is | |
| * created as if by an invocation of | |
| * {@link #createFileHandler(Path) createFileHandler(rootDirectory)}, and is | |
| * associated to a context created as if by an invocation of | |
| * {@link HttpServer#createContext(String) createContext("/")}. The returned | |
| * server is not started. | |
| * | |
| * <p> An output level can be given to print log messages relating to the | |
| * exchanges handled by the server. The log messages, if any, are printed to | |
| * {@code System.out}. If {@link OutputLevel#NONE OutputLevel.NONE} is | |
| * given, no log messages are printed. | |
| * | |
| * @param addr the address to listen on | |
| * @param rootDirectory the root directory to be served, must be an absolute path | |
| * @param outputLevel the log message output level | |
| * @return an HttpServer | |
| * @throws IllegalArgumentException if root does not exist, is not absolute, | |
| * is not a directory, or is not readable | |
| * @throws UncheckedIOException if an I/O error occurs | |
| * @throws NullPointerException if any argument is null | |
| */ | |
| public static HttpServer createFileServer(InetSocketAddress addr, | |
| Path rootDirectory, | |
| OutputLevel outputLevel) { | |
| Objects.requireNonNull(addr); | |
| Objects.requireNonNull(rootDirectory); | |
| Objects.requireNonNull(outputLevel); | |
| try { | |
| var handler = FileServerHandler.create(rootDirectory, MIME_TABLE); | |
| if (outputLevel.equals(OutputLevel.NONE)) | |
| return HttpServer.create(addr, 0, "/", handler); | |
| else | |
| return HttpServer.create(addr, 0, "/", handler, OutputFilter.create(System.out, outputLevel)); | |
| } catch (IOException ioe) { | |
| throw new UncheckedIOException(ioe); | |
| } | |
| } | |
| /** | |
| * Creates a <i>file handler</i> that serves files from a given directory | |
| * path (and its subdirectories). | |
| * | |
| * <p> The file handler resolves the request URI against the given | |
| * {@code rootDirectory} path to determine the path {@code p} on the | |
| * associated file system to serve the response. If the path {@code p} is | |
| * a directory, then the response contains a directory listing, formatted in | |
| * HTML, as the response body. If the path {@code p} is a file, then the | |
| * response contains a "Content-Type" header based on the best-guess | |
| * content type, as determined by an invocation of | |
| * {@linkplain java.net.FileNameMap#getContentTypeFor(String) getContentTypeFor}, | |
| * on the system-wide {@link URLConnection#getFileNameMap() mimeTable}, as | |
| * well as the contents of the file as the response body. | |
| * | |
| * <p> The handler supports only requests with the <i>HEAD</i> or <i>GET</i> | |
| * method, and will reply with a {@code 405} response code for requests with | |
| * any other method. | |
| * | |
| * @param rootDirectory the root directory to be served, must be an absolute path | |
| * @return a file handler | |
| * @throws IllegalArgumentException if rootDirectory does not exist, | |
| * is not absolute, is not a directory, or is not readable | |
| * @throws NullPointerException if the argument is null | |
| */ | |
| public static HttpHandler createFileHandler(Path rootDirectory) { | |
| Objects.requireNonNull(rootDirectory); | |
| return FileServerHandler.create(rootDirectory, MIME_TABLE); | |
| } | |
| /** | |
| * Creates a {@linkplain Filter#afterHandler(String, Consumer) | |
| * post-processing Filter} that prints log messages about | |
| * {@linkplain HttpExchange exchanges}. The log messages are printed to | |
| * the given {@code OutputStream} in {@code UTF-8} encoding. | |
| * | |
| * @apiNote | |
| * To not output any log messages it is recommended to not use a filter. | |
| * | |
| * @param out the stream to print to | |
| * @param outputLevel the output level | |
| * @return a post-processing filter | |
| * @throws IllegalArgumentException if {@link OutputLevel#NONE OutputLevel.NONE} | |
| * is given | |
| * @throws NullPointerException if any argument is null | |
| */ | |
| public static Filter createOutputFilter(OutputStream out, | |
| OutputLevel outputLevel) { | |
| Objects.requireNonNull(out); | |
| Objects.requireNonNull(outputLevel); | |
| return OutputFilter.create(out, outputLevel); | |
| } | |
| } |