Java Single Dependency Dockerized HTTP Endpoint
In this article, we will create a Java-based HTTP endpoint, make an executable jar out of it, pack it up in Docker and run it locally in no time.
This article is intended for beginners, who want to looking for a simple walk-through for running a Java application in Docker.
The vast majority of examples out there describing Java applications in a Dockerized environment include the usage of heavy frameworks like Spring Boot. We want to show here that you don’t need much to get an endpoint running with Java in Docker.
In fact, we will only use a single library as a dependency:HttpMate core. For this example, we’ll use theLowLevel builder of HttpMate with a single HTTP handler.
The environment used for this example
- Java 11+
- Maven 3.5+
- Java-friendly IDE
- Docker version 18+
- Basic understanding of HTTP/bash/Java
The final result is available inthis git repo.
Organizing the Project
Let’s create our initial project structure:
mkdir -p simple-java-http-docker/src/main/java/com/envimate/examples/http
Let’s start with the project’s pom file in the root directory that we called here simple-java-http-docker
:
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.envimate.examples</groupId> <artifactId>simple-java-http-docker</artifactId> <version>0.0.1</version> <dependencies> <dependency> <groupId>com.envimate.httpmate</groupId> <artifactId>core</artifactId> <version>1.0.21</version> </dependency> </dependencies> </project>
Here we have:
- The standard groupId/artifactId/version definition for our project
- The single dependency on the HttpMate core library
This is enough to start developing our endpoint in the IDE of choice. Most of those have support for Maven based Java projects.
Application Entrypoint
To start our little server, we will use a simple main method. Let’s create the entry to our application as an Application.java
file in the directory src/main/java/com/envimate/examples/http
that will for now just output the time to the console.
package com.envimate.examples.http; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public final class Application { public static void main(String[] args) { final LocalDateTime time = LocalDateTime.now(); final String dateFormatted = time.format(DateTimeFormatter.ISO_TIME); System.out.println("current time is " + dateFormatted); } }
Try to run this class and you will see the current time printed.
Let’s make this more functional and separate the part that prints out the time into a lambda function with no argument, a.k.a Supplier
.
package com.envimate.examples.http; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.function.Supplier; public final class Application { public static void main(String[] args) { Supplier handler = () -> { final LocalDateTime time = LocalDateTime.now(); final String dateFormatted = time.format(DateTimeFormatter.ISO_TIME); return "current time is " + dateFormatted; }; System.out.println(handler.get()); } }
The convenience interface provided by the low-level HttpMate does not look much different, except instead of returning a String
, that String
is set to the response, alongside with the indication that everything went well (a.k.a. response code 200).
final HttpHandler httpHandler = (request, response) -> { final LocalDateTime time = LocalDateTime.now(); final String dateFormatted = time.format(DateTimeFormatter.ISO_TIME); response.setStatus(200); response.setBody("current time is " + dateFormatted); };
HttpMate also provides a simple Java HttpServer wrapper – PureJavaEndpoint
– that would allow you to start an endpoint without any further dependency.
All we need to do is give it the instance of the HttpMate:
package com.envimate.examples.http; import com.envimate.httpmate.HttpMate; import com.envimate.httpmate.convenience.endpoints.PureJavaEndpoint; import com.envimate.httpmate.convenience.handler.HttpHandler; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import static com.envimate.httpmate.HttpMate.anHttpMateConfiguredAs; import static com.envimate.httpmate.LowLevelBuilder.LOW_LEVEL; public final class Application { public static void main(String[] args) { final HttpHandler httpHandler = (request, response) -> { final LocalDateTime time = LocalDateTime.now(); final String dateFormatted = time.format(DateTimeFormatter.ISO_TIME); response.setStatus(200); response.setBody("current time is " + dateFormatted); }; final HttpMate httpMate = anHttpMateConfiguredAs(LOW_LEVEL) .get("/time", httpHandler) .build(); PureJavaEndpoint.pureJavaEndpointFor(httpMate).listeningOnThePort(1337); } }
Notice that we have configured our httpHandler to serve the path /time
, when invoked with method GET.
It’s time to start our Application and make some requests:
curl http://localhost:1337/time current time is 15:09:34.458756
Before we put this all into a Dockerfile, we need to package it as a good-old jar.
Building the Jar
We’d need two maven plugins for that:maven-compiler-plugin andmaven-assembly-plugin to build the executable jar.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.envimate.examples</groupId> <artifactId>simple-java-http-docker</artifactId> <version>0.0.1</version> <dependencies> <dependency> <groupId>com.envimate.httpmate</groupId> <artifactId>core</artifactId> <version>1.0.21</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <release>${java.version}</release> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> <configuration> <archive> <manifest> <mainClass> com.envimate.examples.http.Application </mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
Once we have that, let’s build our jar:
mvn clean verify
And run the resulting jar:
java -jar target/simple-java-http-docker-0.0.1-jar-with-dependencies.jar
Same curl:
curl http://localhost:1337/time current time is 15:14:42.992563
Dockerizing the Jar
The Dockerfile looks quite simple:
FROM openjdk:12 ADD target/simple-java-http-docker-0.0.1-jar-with-dependencies.jar /opt/application.jar EXPOSE 1337 ENTRYPOINT exec java -jar /opt/application.jar
It specifies
FROM
: which image to use as a base. We will use an openjdk image.ADD
: the jar we want to the directory we wantEXPOSE
: the port we are listening onENTRYPOINT
: to the command, we want to execute
To build and tag our docker image, we run the following command from the root of the directory:
docker build --tag simple-java-http-docker .
This will produce a docker image that we can run:
docker run --publish 1337:1337 simple-java-http-docker
Notice that we are passing the --publish
parameter, which indicates that the exposed 1337 port, is available under the 1337 port of the machine.
Same curl:
curl http://localhost:1337/time current time is 15:23:04.275515
And that is it: we have our simple HTTP endpoint dockerized!
The Icing
Of course, this is a simplified example, and the endpoint we wrote is not entirely useful. It demonstrates though that you don’t need tons of libraries just to have a running HTTP endpoint, how easy it is to package a runnable jar, use docker with your java application and the basic usage of the low-level HttpMate.
This kind of two-minute setup can be handy when you need to quickly spin a test HTTP server. The other day I was working on a Twitter-bot (stay tuned for an article about that) and I had to debug what my request really looks like on the receiving side. Obviously, I couldn’t ask Twitter to give me a dump of my request, so I needed a simple endpoint, that would output everything possible about my request.
HttpMate’s handler provides access to an object called MetaData
which is pretty much what it’s called – the meta-data of your request, meaning everything available about your request.
Using that object, we can print everything there is to the request.
public final class FakeTwitter { public static void main(String[] args) { final HttpMate httpMate = HttpMate.aLowLevelHttpMate() .callingTheHandler(metaData -> { System.out.println(metaData); }) .forRequestPath("/*").andRequestMethods(GET, POST, PUT) .build(); PureJavaEndpoint.pureJavaEndpointFor(httpMate).listeningOnThePort(1337); } }
The request path /time
is now replaced with a pattern, capturing all paths, and we can add all the HTTP methods we are interested in.
Running our FakeTwitter server and issuing request:
curl -XGET http://localhost:1337/some/path/with?someParameter=someValue
We’ll see the following output in the console (output formatted for readability: it is a map underneath, so you can format it nicely if you so wish)
{ PATH=Path(path=/some/path/with), BODY_STRING=, RAW_QUERY_PARAMETERS={someParameter=someValue}, QUERY_PARAMETERS=QueryParameters( queryParameters={ QueryParameterKey(key=someParameter)=QueryParameterValue(value=someValue) } ), RESPONSE_STATUS=200, RAW_HEADERS={Accept=*/*, Host=localhost:1337, User-agent=curl/7.61.0}, RAW_METHOD=GET, IS_HTTP_REQUEST=true, PATH_PARAMETERS=PathParameters(pathParameters={}), BODY_STREAM=sun.net.httpserver.FixedLengthInputStream@6053cef4, RESPONSE_HEADERS={}, HEADERS=Headers(headers={HeaderKey(value=user-agent)=HeaderValue(value=curl/7.61.0), HeaderKey(value=host)=HeaderValue(value=localhost:1337), HeaderKey(value=accept)=HeaderValue(value=*/*)}), CONTENT_TYPE=ContentType(value=null), RAW_PATH=/some/path/with, METHOD=GET, LOGGER=com.envimate.httpmate.logger.Loggers$$Lambda$17/0x000000080118f040@5106c12f, HANDLER=com.envimate.examples.http.FakeTwitter$$Lambda$18/0x000000080118f440@68157191 }
Final Words
HttpMate on its own offers much more functionality. However, it is young, is not yet for production use and needs your support! If you like what you read, let us know, by dropping us an email to opensource@envimate.com, or just by trying out HttpMate and leaving a comment in the feedback issue.
Published on Java Code Geeks with permission by Envimate, partner at our JCG program. See the original article here: Java Single Dependency Dockerized HTTP Endpoint Opinions expressed by Java Code Geeks contributors are their own. |