Enterprise Java

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 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
}

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.

Envimate

Envimate is a consulting and software engineering company that provides teams with additional brainpower and capacity in areas of DevOps, CI/CD, AWS, Java/Golang Development, Architecture and Clean Code, while also providing the community with opensource projects and increasing the exposure to best practices of software engineering
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button