JEP 408: Simple Web Server
Owner | Julia Boes |
Type | Feature |
Scope | JDK |
Status | Candidate |
Component | core-libs / java.net |
Discussion | net dash dev at openjdk dot java dot net |
Effort | S |
Duration | S |
Reviewed by | Brian Goetz, Chris Hegarty, Daniel Fuchs |
Created | 2021/01/27 12:47 |
Updated | 2021/03/30 04:32 |
Issue | 8260510 |
Summary
Provide a command-line tool to start a minimal web server that serves static files in the current directory. This low-threshold utility will be useful for prototyping, ad-hoc coding, and testing purposes, particularly in educational contexts.
Goals
-
Offer an out-of-the-box static HTTP file server with easy setup and minimal functionality.
-
Reduce developer activation energy and make the JDK more approachable.
-
Provide a default implementation via the command line together with a small API for programmatic creation and customization.
Non-Goals
-
It is not a goal to provide a feature-rich or commercial-grade server. Far better alternatives exist in the form of server frameworks (e.g., Jetty, Netty, and Grizzly) and production servers (e.g., Tomcat and Apache). These full-fledged and performance-optimized technologies take effort to configure, which is exactly what we want to avoid.
-
It is not a goal to provide security features such as authentication, access control, or encryption. The server is intended solely for testing, development, and debugging. Accordingly, its design is explicitly minimal so as to avoid confusion with a full-featured server application.
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:
-
Web development testing, where a local testing server is used to simulate a client-server set up.
-
Web-service or application testing, where static files are used as API stubs in a directory structure that mirrors RESTful URLs and contains dummy data.
-
Informal browsing and sharing of files across systems to, e.g., search a directory on a remote server from your local machine.
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
Command-line tool
The following command starts the server:
$ java -m jdk.httpserver [-b bind address] [-p port] [-d directory] \
[-h to show help message] [-o none|default|verbose]
The command-line options can be used to specify the bind address and port, the directory, and the output format for request information. If startup is successful then the server prints a message to System.out
listing the local address and the absolute path of the directory being served. For example:
$ java -m jdk.httpserver -b 127.0.0.1
Serving /current/directory (and subdirectories) on http://127.0.0.1:8000/ ...
The output filter, which can be set via the -o
option, prints information about HTTP requests. With the output format set to default
or verbose
, information about each request is printed upon receiving it. For example, the default
format produces output of the form:
127.0.0.1 - - [10/Feb/2021:14:34:11 +0000] "GET /some/subdirectory/" 200 –
The verbose
format 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 will display a help message.
Implementation & tool provider
The command above runs the main method of the unexported implementation class, which is the entry point of the existing jdk.httpserver
module. The implementation uses the existing public APIs HttpServer
, HttpHandler
, and Filter
in the com.sun.net.httpserver
package. The server runs in the foreground and binds to the wildcard address and port 8000 by default. The server handles only the idempotent HEAD and GET requests. It maps GET requests to the structure of the given directory, with the current working directory as default.
If the requested resource is a file, its content is served. If the requested resource is a directory that contains an index file, the content of the index file is served. Otherwise, the names of all files and subdirectories of the directory are listed. Symbolic links and hidden files are not listed or served.
MIME types are supported via the java.net.URLConnection::getFileNameMap
API.
The jdk.httpserver
module will provide the java.util.spi.ToolProvider
service for programmatic access to the command-line tool.
API
While the command-line tool is useful, what if one wants to use the components of the simple 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 API, we define new APIs for server creation and customized request handling.
The new classes are SimpleFileServer
, HttpHandlers
and Request
, each built on the existing jdk.httpserver
abstractions 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(int port, Path root,
OutputLevel outputLevel) {...}
public static HttpHandler createFileHandler(Path root) {...}
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(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.DEFAULT);
jshell> var server = HttpServer.create(new InetSocketAddress(8080),
...> 10, "/store/", new SomePutHandler(), filter);
jshell> server.start();
These 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 server is provided by its handler. To support adapting or extending this handler for use with existing code we introduce a new HttpHandlers
class with two static combinator methods for handler customization:
package com.sun.net.httpserver;
public class HttpHandlers {
public static HttpHandler handleOrElse(Predicate<Request> handlerTest,
HttpHandler handler,
HttpHandler fallbackHandler)
throws IOException {...}
public static HttpHandler adaptRequest(HttpHandler handler,
UnaryOperator<Request> operator)
{...}
}
handleOrElse
complements a conditional handler with another handler, while adaptRequest
allows a handler to inspect and adapt certain properties of a request before handling it. Use cases for these methods include adding a header to all incoming requests, or delegating exchanges based on the request method.
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 handler = HttpHandlers.adaptRequest(h, r -> r.with("Foo",
...> List.of("Bar")));
jshell> var s = HttpServer.create(new InetSocketAddress(8080),
...> 10, "/", handler);
jshell> s.start();
Alternatives
We considered several API alternatives during prototyping:
-
A new class
DelegatingHandler
— Bundle the customization methods in a separate class that implements theHttpHandler
interface. We discarded this option since it comes at the cost of introducing a new type without adding more functionality. This new type would also be hard to discover. TheHttpHandlers
class, on the other hand, uses the pattern of outboarding, where static helper methods or factories of a class are bundled in a new class. The almost-identical name makes it easy to find the class, facilitates the understanding and use of the new API points, and hides the implementation details of delegation. -
HttpHandler
as a service — TurnHttpHandler
into a service and provide an internal file-server handler implementation. The developer could either provide a custom handler or use the default provider. The disadvantage of this approach is that it is more difficult to use and rather elaborate for the small set of functionality we want to provide. -
Filter
instead ofHttpHandler
— Use a filter instead of a handler to process the request. Filters can access a request before or after the handler is invoked, for example for authentication or logging. However, they are not widely used, and were not designed to fully replace handlers. Using them in this way would be counter-intuitive and the methods would be harder to find.
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.