Create APIs with jwebserver from jshell to docker
In the age of cloud computing it is handy to have the ability to run 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 servers 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 environment. Let’s start
Running a command-line
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 allows you to customize the default server settings, a bit. The most obvious one 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.)
Image 1.:: “jwebserver” delivers a context
├── 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:
- Only HTTP methods GET and HEAD are allowed
- HTTP/1.1 is supported, but 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
$ 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 closer newly gained prototyping possibilities by using jshell.
How to use jwebserver in R-E-P-L
The jshell was added to the JDK 9 release and its module jdk.jshell. Jsheel is Read-Evaluate-Print Loop, shortly REPL. It gives an ability to create instances, variables, expressions, statements etc. We can directly execute the code “snippets” by entering and getting the results. How useful is the jshell together with the newly added
$ jshell | Welcome to JShell -- Version 18 | For an introduction type: /help intro jshell > import com.sun.net.httpserver.SimpleFileServer; // AND ALL REQUIRED CLASSES jshell > var server = SimpleFileServer.createFileServer(new InetSocketAddress(8088), Paths.get("./").toAbsolutePath(), OutputLevel.VERBOSE); jshell > // Start web-server from the directory jshell > var server = SimpleFileServer.createFileServer(new InetSocketAddress(8088), Paths.get("./").toAbsolutePath(), OutputLevel.VERBOSE); output: server ==> sun.net.httpserver.HttpServerImpl@6659c656 jshell > server.st //try Auto-complete output: server.s setExecutor( start() stop( jshell > // Auto-complete server the option, Method stop requires a parameter, delay in seconds jshell > server.start() // Server started and context served on the port 8088 (Image 1.) jshell > server.stop(1) // server is stopped with 1 second delay
Example 9.: Creating an instance of web-server in jshell, starting and stopping it
The jshell is pretty handy as the instance of the server is created and can be started or stopped on demand.
Let us explore a bit the programmatic possibilities as command-line and jshell ones may already be pretty useful, IMHO.
SimpleFileServer resides in the module “com.sun.net.httpserver” for the blog post purposes we create a new class
WebServerSimpleMain and create it executable by defining a “main” method. Our journey starts with the 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 8.: 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 9.: 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
Let us consider an example where we want to create a web-server. Such a web-server allows the methods GET, POST (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 10.: Starting HttpServer with custom Handlers and output Filters
Closer look at the custom handlers (Example 10.,
notAllowedHandler) shows their concatenation.
Running in docker
jwebserver can be pretty handy as it allows to execute it just as a command
$ docker run (Example 10.) or create a 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 the 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 10.: 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"
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 11.: Dockerfile snippet
The newly added
jwebserver is pretty neat feature. The mentioned examples showed how to easily configure different approaches and even start them as containers.
jwebserver comes with JDK 18. It is a hot candidate for the cases where only simple content or responses are required and reduces the necessity to search for more complicated solutions like Jetty, Netty or etc.
- GitHub Project, JVM-Lanuage-Examples
- JEP 408: Simple Web Server
- Foojay.io: How to create APIs with jwebserver ready for docker