Foojay Today

How to Create APIs with the Java 18 jwebserver Ready for Docker 

April 11, 2022

In the age of cloud computing, it is at times very helpful to have the ability to run a very simple web server. A simple web server that delivers static content.

Maybe some of us are already familiar with the neat python util “SimpleHTTPServer” (Example 1.). It allows you to run a web server from the current location and obtain access to static content.

Although such a server is very simple and even without the ability to define a directory, it is very helpful in many cases. For example, exposing a simple end-point that serves a JSON response:

$ python -m SimpleHTTPServer 9999 

Example 1.: Executing simple python web-server in the current directory to serve a content

This was not possible in Java out of the box. Luckily this has changed with Java 18. It came with similar features as Python through JEP-408 (Reference 2.). Yes, a Simple Web Server.

The goal of the article  is to provide a closer look at the associated “jwebserver” features and not only in dockerized environments.

Let’s begin!

How to start from the command-line...

Image 1.:  “jwebserver” delivers context, using the code below

The simplest way to start  “jwebserver” is via the command-line.

Compared to the Python version (Example 1.)  “jwebserver” provides a couple of options that allow you to customise the default server settings, a bit.

The most obvious option is that the server allows you to specify a served directory (Example 2.) and a couple of other options as seen in Example 3. The directory contains the file “index.html” which is used as the default entry point (Image 1.)

├── get_simple_1.json
├── get_simple_2.json
├── images
│   └── OpenJDK_logo.png
└── index.html

Example 2.: Directory structure for the command-line usage

$ jwebserver -b 0.0.0.0 -p 8880 -d <PROJECT_PATH>/http-static -o info 

Output: 
<PROJECT_PATH>/http-static and subdirectories on 0.0.0.0 (all interfaces) port 8880 URL http://<IP_ADDRESS>:8880/ 

Example 3.: Command to start the server with options “-b” for binding interfaces, “-p” specified port, “-d” served directory or “-o” logging level none,info,verbose

$ curl -X GET http://localhost:8880
<!DOCTYPE html>
<html>
<body>

Example 4.: http GET method request

$ curl --head http://localhost:8880
HTTP/1.1 200 OK
Date: Fri, 08 Apr 2022 17:29:37 GMT
Last-modified: Thu, 7 Apr 2022 21:31:11 GMT
Content-type: text/html
Content-length: 465

Example 5.: http head request

$ curl -X GET -I http://localhost:8880/get_simple_1.json
HTTP/1.1 200 OK
Date: Fri, 08 Apr 2022 17:43:08 GMT
Last-modified: Fri, 8 Apr 2022 14:08:42 GMT
Content-type: application/json
Content-length: 50

Example 6.: http method GET with JSON response

$ curl -X POST -I http://localhost:8880
HTTP/1.1 405 Method Not Allowed
Date: Fri, 08 Apr 2022 17:39:21 GMT
Allow: HEAD, GET
Content-length: 0

Example 7.: Not supported command line methods POST, PUT, DELETE etc.

A nice feature presents itself! The “jwebserver”  serves static content and it allows to continually modify served content. Isn't it cool? Yes, it is.

There are some limitations to command-line usage, let us highlight some of them:

  • HTTP methods GET and HEAD are allowed
  • HTTP/1.1 supported, no HTTPS
  • HTML files served as “Content-type: text/html
  • JSON files served as “Content-type: application/json” 
  • Default logging is set to INFO, option “-o” (Example 3.)
  • Other HTTP methods result in 405 - Method Not Allowed state (Example 7.)


It may be useful to point out that the “jwebserver” command is the wrapper to the Java executable “main()” method of the class “sun.net.httpserver.simpleserver.Main”. The Java class is resides in the module “jdk.httpserver”.

$ java -m jdk.httpserver  -b 0.0.0.0 -p 8880 -d <PROJECT_PATH>/http-static -o verbose

Example 8.: Using Java to execute the “jdk.httpserver” default module method “main” with options similar to Example 3.

Let us explore a bit the programmatic possibilities as command-line ones may already be pretty useful, IMHO.

Starting “SimpleFileServer” Programmatically 

The class “SimpleFileServer” resides in the module “com.sun.net.httpserver”.

For purposes of this article, we create a new class “WebServerSimpleMain” and make it executable by defining a “main” method.

Our journey starts with basic programmatic server initiation.

Simple server serves a directory

The example shows how to create a server with an absolute folder path (Example 8.). This path is used as the source for the served files and its subdirectories (Example 9.).

It means that the started server has access to the all available content inside the initiated server context. The folder does not contain the “index.html” file which implies that the content is printed out (Image 2.)

├── get
│   └── get_request.json
├── get_simple.json
└── post
    └── post_request.json

Example 9.: “http-static” folder structure

var servedPath = Paths.get("http-static").toAbsolutePath();
var server = SimpleFileServer.createFileServer(
       new InetSocketAddress(SIMPLE_SERVER_PORT), servedPath, OutputLevel.VERBOSE);
server.start();

Example 10.: Programmatic simple file server initiation and start

Image 2.: Default configuration print the folder structure

Adding request handlers and filters to HttpServer

The JEP-408 (Reference 2.) mentions that the class “SimpleFileServer”, added in Java 18, is based on the already existing "HttpServer" class that has been present since Java 1.6.  In this section we explore its underlying components “HttpServer”, “HttpHandler” and “OutputFilter

Let us consider an example where we want to create a web-server. Such a web-server allows the methods “GET”, “POST” d (Example 10.). In case of disallowed methods it returns a message “Sorry, not allowed method”. 

In this case, we need to extend the previously mentioned three components.  For each request type we also define its own file that holds the response (Example 8.).

...
//GET, POST Handlers with the content and predicates
var getHandler = HttpHandlers.of(200, jsonHeaders, Files.readString(getResponsePath));
var postHandler = HttpHandlers.of(200, jsonHeaders, Files.readString(postResponsePath));
Predicate<Request> IS_GET = r -> r.getRequestMethod().equals("GET");
Predicate<Request> IS_POST = r -> r.getRequestMethod().equals("POST");
var notAllowedHandler = HttpHandlers.of(405, 
     Headers.of("Access-Control-Allow-Methods", "GET,POST"), 
    "Sorry, not allowed method");

var h1 = HttpHandlers.handleOrElse(IS_GET, getHandler, notAllowedHandler);
var h2 = HttpHandlers.handleOrElse(IS_POST, postHandler, h1);
var server = HttpServer.create(new InetSocketAddress(HANDLER_SERVER_PORT), 2,
       "/", h2, SimpleFileServer.createOutputFilter(System.out, OutputLevel.INFO));
server.start();
...

Example 11.: Starting HttpServer with custom Handlers and output Filters

A closer look at the custom handlers (Example 10., getHandler,h1, postHandler,h2, notAllowedHandler) shows their concatenation.

Running in Docker

The “jwebserver” can be pretty handy as it allows to execute it just as a command “$ docker run” (Example 10.) or create Docker containers with a mounted directory that can be used in a docker-compose file (Example 11.).

The mounted directory content can be continually updated based on your needs (adding, updating, removing static files).

$ docker run  -p 8000:8000 -t -i -v <PROJECT_PATH>/http-static:/http-static -w /http-static openjdk:18-jdk-slim jwebserver -b 0.0.0.0

Example 12.: running command line “jwebserver” version from the docker image with mounted directory

...
jserver_one:
   image: jdk18-slim-jwebserver:latest
   restart: always
   ports:
     - "8000:8000"
 jserver_two:
   image: openjdk:18-jdk-slim
   restart: always
   volumes:
     - ./http-static:/http-static
   ports:
     - "8001:8001"
...

Example 13.: docker-compose file snipped, created image by Dockerfile(Example 12.) and mounting directory

FROM openjdk:18-jdk-slim
RUN mkdir /http-static
RUN mkdir /http-static/images
COPY http-static/ /http-static/
WORKDIR /http-static
CMD ["sh", "-c", "jwebserver -b 0.0.0.0"]

Example 14.: Dockerfile snippet

Conclusion

The newly added “jwebserver” is a pretty neat feature.

The mentioned examples above show how easy it is to configure different approaches and even start them as containers.

The “jwebserver” comes with Java 18.

It is a hot candidate for cases where only simple content or responses are required and reduces the necessity to search for more complicated solutions like Jetty and Netty.

References

  1. GitHub Project, JVM-Lanuage-Examples : https://github.com/mirage22/jvm-language-examples
  2. JEP 408: Simple Web Server: https://openjdk.java.net/jeps/408
 
Topics:

Author(s)

  • Miro Wengner

    Miro has been a member of the Java Community Process (JCP) for a very long time. He contributes actively to the OpenJDK and Java Mission Control/Flight Recorder project. His focus ... Learn more

Comments (0)

Your email address will not be published.

Highlight your code snippets using [code lang="language name"] shortcode. Just insert your code between opening and closing tag: [code lang="java"] code [/code]. Or specify another language.

Save my name, email, and website in this browser for the next time I comment.

Subscribe to foojay updates:

https://foojay.io/feed/
Copied to the clipboard