Friday, April 15, 2022

Java 18’s Simple Web Server: A tool for the command line and beyond

Core Java, Oracle Java Exam, Oracle Java Certification, Oracle Java Certification, Oracle Java Career, Oracle Java Jobs, Oracle Java Skill

Learn how the new minimal server can make your life easier in the context of ad hoc coding, testing, prototyping, and debugging.

Java 18’s Simple Web Server is a minimal HTTP static file server that was added in JEP 408 to the jdk.httpserver module. It serves a single directory hierarchy, and it serves only static files over HTTP/1.1; dynamic content and other HTTP versions are not supported.

The web server’s specification is informed by the overarching goal of making the JDK more approachable. The server is an out-of-the-box tool with easy setup and minimal functionality that lets you hit the ground running and focus on the task at hand. The simplistic design also avoids any confusion with feature-rich or commercial-grade servers—after all, far better alternatives exist for production environments, and the Simple Web Server is not the right choice in such cases. Instead, this server shines in the context of prototyping, ad hoc coding, and testing.

The server supports only the HEAD and GET request methods; any other requests receive either a 501 - Not Implemented or a 405 - Not Allowed response. HEAD and GET requests are handled as follows:

◉ If the requested resource is a file, its content is served.

◉ If the requested resource is a directory with an index file, the content of the index file is served.

◉ Otherwise, the directory listing is returned.

The jwebserver command-line tool

The jwebserver tool comes with the following usage options:

jwebserver [-b bind address] [-p port] [-d directory]

           [-o none|info|verbose] [-h to show options]

           [-version to show version information]

Each option has a short and a long version, and there are conventional options for printing the help message and the version information. Here are the usage options.

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

◉ -b addr or --bind-address addr: Specifies the address to bind to. The default is 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. The default is the current directory.

◉ -o level or --output level: Specifies the output format. The levels are none, info, and verbose. The default is info.

◉ -p port or --port port: Specifies the port to listen on. The default is 8000.

◉ -version or --version: Prints the Simple Web Server’s version information and exits.

Starting the server

The following command starts the Simple Web Server:

$ jwebserver

By default, the server binds to the loopback address and port 8000 and serves the current working directory. If startup is successful, the server runs in the foreground and prints a message to System.out listing the local address and the absolute path of the directory being served, such as /cwd. 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/

Configuring the server

You can change the default configuration by using the respective options. For example, here is how to bind the Simple Web 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/

Warning: This command makes the server accessible to all hosts on the network. Do not do this unless you are sure the server cannot leak any sensitive information.

As another example, here is how to run the server on port 9000.

$ jwebserver -p 9000

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

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

You can change the logging output 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.

Stopping the server

Once it is started successfully, the Simple Web Server runs until it is stopped. On UNIX platforms, the server can be stopped by sending it a SIGINT signal, which is done by pressing Ctrl+C in a terminal window.

The descriptions above about how to start, configure, and stop the server capture the full extent of the functionality of jwebserver. The server is minimal yet configurable enough to cover common use cases in web development and web services testing, as well as for file sharing or browsing across systems.

While the jwebserver tool certainly comes in handy in many scenarios, what if you want to use the components of the Simple Web Server with existing code or further customize them? That’s where a set of new API points come in.

The new com.sun.net.httpserver API points

To bridge the gap between the simplicity of the command-line tool and the write-everything-yourself approach of the com.sun.net.httpserver API, JEP 408 introduces a new set of API points for server creation and customization. (The com.sun.net.httpserver package has been included in the JDK since 2006.)

The new class SimpleFileServer offers the key components of the server via three static methods. These methods allow you to retrieve a server instance, a file handler, or an output filter in a straightforward fashion and then custom tailor or combine those functions with existing code as needed.

Retrieving a server instance. The createFileServer method returns a static file server that’s configured with a bind address and port, a root directory to be served, and an output level. The returned server can be started or configured further, as follows.

Note: The source code examples in this article use jshell, Java’s convenient read-eval-print loop (REPL) shell.

jshell> import com.sun.net.httpserver.*;

jshell> var server = SimpleFileServer.createFileServer(new InetSocketAddress(8080), 

   ...> Path.of("/some/path"), OutputLevel.VERBOSE); 

jshell> server.start()

Retrieving a file handler instance. The createFileHandler method returns a file handler that serves a given root directory that can be added to a new or existing server. Note the overloaded HttpServer::create method, which is a nice addition to the API that allows you to initialize a server with a handler in one call.

jshell> var handler = SimpleFileServer.createFileHandler(Path.of("/some/path"));

jshell> var server = HttpServer.create(new InetSocketAddress(8080), 

   ...> 10, "/somecontext/", handler); 

jshell> server.start();

Retrieving an output filter. The createOutputFilter method takes an output stream and an output level and returns a logging filter that can be added to an existing server.

jshell> var filter = SimpleFileServer.createOutputFilter(System.out, 

   ...> OutputLevel.INFO); 

jshell> var server = HttpServer.create(new InetSocketAddress(8080), 

   ...> 10, "/somecontext/", new SomeHandler(), filter); 

jshell> server.start();

Doubling down on handlers

Now that the server components can easily be retrieved, the Simple Web Server team wanted to enhance the composability of the existing com.sun.net.httpserver API. In particular the team doubled down on the creation and combination of handlers, which are at the core of the request handling logic.

For this, the team introduced the HttpHandlers class, which comes with two new methods.

HttpHandlers::of returns a canned response handler with fixed state, namely a status code, a set of headers, and a response body.

◉ HttpHandlers::handleOrElse combines two handlers on a condition.

Here’s an example of how they can be used.

jshell> Predicate<Request> IS_GET = r -> r.getRequestMethod().equals("GET");

jshell> var jsonHandler = HttpHandlers.of(200, 

   ...> Headers.of("Content-Type", "application/json"),

   ...> Files.readString(Path.of("some.json")));

jshell> var notAllowedHandler = HttpHandlers.of(405, Headers.of("Allow", "GET"), "");

jshell> var handler = HttpHandlers.handleOrElse(IS_GET, jsonHandler, notAllowedHandler);

In the example above, the jsonHandler is a canned response handler that always returns a status code of 200, the given header, and the content of a given JSON file as the response body. The notAllowedHandler, on the other hand, always returns a 405 response. Based on the predicate, the combined handler checks the request method of an incoming request and forwards the request to the appropriate handler.

Taken together, these two new methods make a neat couple for writing custom-tailored logic for request handling and, thus, they can help you tackle various testing and debugging scenarios.

Adapting request state

Speaking of testing and debugging, there are other times when you might want to inspect and potentially adapt certain properties of a request before handling it.

To support this, you can use Filter.adaptRequest, a method that returns a preprocessing filter that can read and optionally change the URI, the method, or the headers of a request.

jshell> var filter = Filter.adaptRequest("Add Foo header", 

   ...> request -> request.with("Foo", List.of("Bar"))); 

jshell> var server = HttpServer.create(new InetSocketAddress(8080), 10, "/", someHandler, 

   ...> filter); 

jshell> server.start();

In the example above, the filter adds a Foo header to each incoming request before the someHandler gets a go at it. This functionality enables the straightforward adaptation and extension of an existing handler.

Going even further

With these new API points at hand, other less-obvious yet interesting applications for the Simple Web Server are within reach. For example, the Simple File Server API can be used for creating an in-memory file server, which serves a .zip file system or a Java runtime directory. For details and code snippets of these and other examples, see my recent article, “Working with the Simple Web Server.

Source: oracle.com

Related Posts

0 comments:

Post a Comment