JEP 408: Simple Web Server

OwnerJulia Boes
TypeFeature
ScopeJDK
StatusCompleted
Release18
Componentcore-libs / java.net
Discussionnet dash dev at openjdk dot java dot net
EffortS
DurationS
Reviewed byAlex Buckley, Brian Goetz, Chris Hegarty, Daniel Fuchs
Endorsed byBrian Goetz
Created2021/01/27 12:47
Updated2022/01/18 16:08
Issue8260510

Summary

Provide a command-line tool to start a minimal web server that serves static files only. No CGI or servlet-like functionality is available. This tool will be useful for prototyping, ad-hoc coding, and testing purposes, particularly in educational contexts.

Goals

Non-Goals

Motivation

A common rite of passage for developers is to serve a file on the web, likely a “Hello, world!” HTML file. Most computer science curricula introduce students to web development, where local testing servers are commonly used. Developers usually also learn about system administration and web services, other areas where development tools with basic server functionality can come in handy. Educational and informal tasks such as these are where a small out-of-the-box server is desirable. Use cases include:

In all these cases we can, of course, use a web-server framework, but that approach has a high activation energy: We have to look for options, pick one, download it, configure it, and figure out how to use it before we can serve our first request. These steps amount to quite a bit of ceremony, which is a drawback; getting stuck somewhere on the way can be frustrating and might even hinder the further use of Java. A basic web server spun up from the command line or via a few lines of code lets us bypass this ceremony, so that we can instead focus on the task at hand.

Python, Ruby, PHP, Erlang, and many other platforms offer out-of-the-box servers run from the command line. This variety of existing alternatives demonstrates a recognized need for this type of tool.

Description

The Simple Web Server is a minimal HTTP server for serving a single directory hierarchy. It is based on the web server implementation in the com.sun.net.httpserver package that has been included in the JDK since 2006. The package is officially supported, and we extend it with APIs to simplify server creation and enhance request handling. The Simple Web Server can be used via the dedicated command-line tool jwebserver or programmatically via its API.

Command-line tool

The following command starts the Simple Web Server:

$ jwebserver

If startup is successful then jwebserver prints a message to System.out listing the local address and the absolute path of the directory being served. For example:

$ jwebserver
Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving /cwd and subdirectories on 127.0.0.1 port 8000
URL: http://127.0.0.1:8000/

By default, the server runs in the foreground and binds to the loopback address and port 8000. This can be changed with the -b and -p options. For example, to run the server on port 9000, use:

$ jwebserver -p 9000

For example, to bind the server to all interfaces:

$ jwebserver -b 0.0.0.0
Serving /cwd and subdirectories on 0.0.0.0 (all interfaces) port 8000
URL: http://123.456.7.891:8000/

By default, files are served from the current directory. A different directory can be specified with the -d option.

Only idempotent HEAD and GET requests are served. Any other requests receive a 501 - Not Implemented or a 405 - Not Allowed response. GET requests are mapped to the directory being served, as follows:

The Simple Web Server supports HTTP/1.1 only. There is no HTTPS support.

MIME types are configured automatically. For example, .html files are served as text/html and .java files are served as text/plain.

By default, every request is logged on the console. The output looks like this:

127.0.0.1 - - [10/Feb/2021:14:34:11 +0000] "GET /some/subdirectory/ HTTP/1.1" 200 -

Logging output can be changed with the -o option. The default setting is info. The verbose setting additionally includes the request and response headers as well as the absolute path of the requested resource.

Once started successfully, the server runs until it is stopped. On Unix platforms, the server can be stopped by sending it a SIGINT signal (Ctrl+C in a terminal window).

The -h option displays a help message listing all options, which follow the guidelines in JEP 293. A jwebserver man page is also available.

Options:
       -h or -? or --help
              Prints the help message and exits.

       -b addr or --bind-address addr
              Specifies the address to bind to.  Default: 127.0.0.1 or ::1 (loopback).  For
              all interfaces use -b 0.0.0.0 or -b ::.

       -d dir or --directory dir
              Specifies the directory to serve.  Default: current directory.

       -o level or --output level
              Specifies the output format.  none | info | verbose.  Default: info.

       -p port or --port port
              Specifies the port to listen on.  Default: 8000.

       -version or --version
              Prints the version information and exits.

       To stop the server, press Ctrl + C.

API

While the command-line tool is useful, what if one wants to use the components of the Simple Web Server (i.e., server, handler, and filter) with existing code, or further customize the behavior of the handler? While some configuration is possible on the command line, a concise and intuitive programmatic solution for creation and customization would improve the utility of the server components. To bridge the gap between the simplicity of the command-line tool and the write-it-yourself approach of the current com.sun.net.httpserver API, we define new APIs for server creation and customized request handling.

The new classes are SimpleFileServer, HttpHandlers, and Request, each built on existing classes and interfaces in the com.sun.net.httpserver package: HttpServer, HttpHandler, Filter, and HttpExchange.

The SimpleFileServer class supports the creation of a file server, a file-server handler, and an output filter:

package com.sun.net.httpserver;

public final class SimpleFileServer {
    public static HttpServer createFileServer(InetSocketAddress addr,
                                              Path rootDirectory,
                                              OutputLevel outputLevel) {...}
    public static HttpHandler createFileHandler(Path rootDirectory) {...}
    public static Filter createOutputFilter(OutputStream out,
                                            OutputLevel outputLevel) {...}
    ...
}

With this class, a minimal yet customized server can be started in a few lines of code in jshell:

jshell> var server = SimpleFileServer.createFileServer(new InetSocketAddress(8080),
   ...> Path.of("/some/path"), OutputLevel.VERBOSE);
jshell> server.start()

A customized file-server handler can be added to an existing server:

jshell> var server = HttpServer.create(new InetSocketAddress(8080),
   ...> 10, "/store/", new SomePutHandler());
jshell> var handler = SimpleFileServer.createFileHandler(Path.of("/some/path"));
jshell> server.createContext("/browse/", handler);
jshell> server.start();

A customized output filter can be added to a server during creation:

jshell> var filter = SimpleFileServer.createOutputFilter(System.out,
   ...> OutputLevel.INFO);
jshell> var server = HttpServer.create(new InetSocketAddress(8080),
   ...> 10, "/store/", new SomePutHandler(), filter);
jshell> server.start();

The last two examples are enabled by new overloaded create methods in the HttpServer and HttpsServer classes:

public static HttpServer create(InetSocketAddress addr,
                                int backlog,
                                String root,
                                HttpHandler handler,
                                Filter... filters) throws IOException {...}

Enhanced request handling

The core functionality of the Simple Web Server is provided by its handler. To support extending this handler for use with existing code, we introduce a new HttpHandlers class with two static methods for handler creation and customization as well as a new method in the Filter class for adapting a request:

package com.sun.net.httpserver;

public final class HttpHandlers {
    public static HttpHandler handleOrElse(Predicate<Request> handlerTest,
                                           HttpHandler handler,
                                           HttpHandler fallbackHandler) {...}
    public static HttpHandler of(int statusCode, Headers headers, String body) {...}
    {...}
}

public abstract class Filter {
    public static Filter adaptRequest(String description,
                                      UnaryOperator<Request> requestOperator) {...}
    {...}
}

handleOrElse complements a conditional handler with another handler, while the factory method of lets you create handlers with pre-set response state. The pre-processing filter obtained from adaptRequest can be used to inspect and adapt certain properties of a request before handling it. Use cases for these methods include delegating exchanges based on the request method, creating a "canned response" handler that always returns a certain response, or adding a header to all incoming requests.

The existing API captures an HTTP request as part of a request-response pair represented by an instance of the HttpExchange class, which describes the full and mutable state of an exchange. Not all of this state is meaningful for handler customization and adaptation. We therefore introduce a simpler Request interface to provide a limited view of the immutable request state:

public interface Request {
    URI getRequestURI();
    String getRequestMethod();
    Headers getRequestHeaders();
    default Request with(String headerName, List<String> headerValues)
    {...}
}

This enables the straightforward customization of an existing handler, for example:

jshell> var h = HttpHandlers.handleOrElse(r -> r.getRequestMethod().equals("PUT"),
   ...> new SomePutHandler(), new SomeHandler());
jshell> var f = Filter.adaptRequest("Add Foo header", r -> r.with("Foo", List.of("Bar")));
jshell> var s = HttpServer.create(new InetSocketAddress(8080),
   ...> 10, "/", h, f);
jshell> s.start();

Alternatives

We considered an alternative for the command-line tool:

We considered several API alternatives during prototyping:

Testing

The core functionality of the command-line tool is provided by the API, so most of our testing effort will focus on the API. The API points can be tested in isolation with unit tests and the existing test framework. We will focus particularly on file-system access and URI sanitization. We will complement the API tests with command and sanity testing of the command-line tool.

Risks and Assumptions

This simple server is intended for testing, development, and debugging purposes only. Within this scope the general security concerns of servers apply, and will be addressed by following security best practices and thorough testing.