Java single dependency Dockerized HTTP endpoint

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 the LowLevel 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 in this 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>
&lt;groupId&gt;com.envimate.examples&lt;/groupId&gt;
&lt;artifactId&gt;simple-java-http-docker&lt;/artifactId&gt;
&lt;version&gt;0.0.1&lt;/version&gt;

&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;com.envimate.httpmate&lt;/groupId&gt;
        &lt;artifactId&gt;core&lt;/artifactId&gt;
        &lt;version&gt;1.0.21&lt;/version&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;

</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 and maven-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>

&lt;groupId&gt;com.envimate.examples&lt;/groupId&gt;
&lt;artifactId&gt;simple-java-http-docker&lt;/artifactId&gt;
&lt;version&gt;0.0.1&lt;/version&gt;

&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;com.envimate.httpmate&lt;/groupId&gt;
        &lt;artifactId&gt;core&lt;/artifactId&gt;
        &lt;version&gt;1.0.21&lt;/version&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;

&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
            &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
            &lt;version&gt;3.8.1&lt;/version&gt;
            &lt;configuration&gt;
                &lt;release&gt;${java.version}&lt;/release&gt;
                &lt;source&gt;${java.version}&lt;/source&gt;
                &lt;target&gt;${java.version}&lt;/target&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
            &lt;artifactId&gt;maven-assembly-plugin&lt;/artifactId&gt;
            &lt;executions&gt;
                &lt;execution&gt;
                    &lt;phase&gt;package&lt;/phase&gt;
                    &lt;goals&gt;
                        &lt;goal&gt;single&lt;/goal&gt;
                    &lt;/goals&gt;
                    &lt;configuration&gt;
                        &lt;archive&gt;
                            &lt;manifest&gt;
                                &lt;mainClass&gt;
                                    com.envimate.examples.http.Application
                                &lt;/mainClass&gt;
                            &lt;/manifest&gt;
                        &lt;/archive&gt;
                        &lt;descriptorRefs&gt;
                            &lt;descriptorRef&gt;jar-with-dependencies&lt;/descriptorRef&gt;
                        &lt;/descriptorRefs&gt;
                    &lt;/configuration&gt;
                &lt;/execution&gt;
            &lt;/executions&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;

</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:

ROM 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 want.
  • EXPOSE: the port we are listening on.
  • ENTRYPOINT: 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
}

Thanks for reading. If you liked this post, share it with all of your programming buddies!

Further reading

☞ Build a Basic App with Spring Boot and JPA using PostgreSQL

☞ Build a Simple CRUD App with Spring Boot and Vue.js

☞ Introducing TensorFlow.js: Machine Learning in Javascript

☞ An illustrated guide to Kubernetes Networking

☞ Google Kubernetes Engine By Example

☞ AWS DevOps: Introduction to DevOps on AWS

☞ Docker Tutorial for Beginners

☞ Kotlin Microservices With Micronaut, Spring Cloud, and JPA


Originally published on https://dzone.com

#docker #web-development #spring-boot #java

Java single dependency Dockerized HTTP endpoint
3 Likes6.40 GEEK