[HttpServer series] Getting Started - First context (endpoint)
Tags: java, learning, frameworks
Published: 2024-01-05 14:35:24

---
title: [HttpServer series] Getting Started - First context (endpoint)
published: true
date: 2024-01-05 11:59:50 UTC
tags: java, learning, frameworks
canonical_url: https://blog.mandraketech.in/http-server-getting-started
series: Project No-Web-Framework-No-Annotations using Java
---

Continuing in the series, let's write the first endpoint, a "ping", that responds a "pong". The HttpServer uses the `HttpHandler` interface to delegate the handling of the "contexts". I find the name "endpoint" more reasonable name. But `Context` is a term that has been used since the first Servlet spec was published. So, I think this is reasonable.

Note: You can see the code changes for this article in my MR, but really, read the article before you go there. So, I will put it at the bottom.

### The Ping Handler

```
class PingHandler implements HttpHandler { @Override public void handle(HttpExchange he) throws IOException { try (var os = he.getResponseBody()) { he.sendResponseHeaders(200, 0); os.write("pong".getBytes()); } }}
```

Let me explain the pieces of this code, though this was code suggested by Cody AI. The key pieces here are:

`he.getResponseBody()` is the output stream. Unless this is closed, the response is not sent back, and the http client will wait. This is the reason it is encapsulated in a `try (resource)` block. The `OutputStream` is `AutoCloseable`.

`he.sendResponseHeaders` sets the response code to success.

### What about all other URIs ?

When running a server, it is always important to handle the "Not Found" use case. Thankfully, in this case, if we do not do anything, the `HttpServer` will send an HTML output and a `404` response. So, lets write one for ourselves.

```
class RootHandler implements HttpHandler { @Override public void handle(HttpExchange he) throws IOException { try (var os = he.getResponseBody()) { he.sendResponseHeaders(404, 0); os.write("Not Found !!!".getBytes()); } }}
```

Nothing spectacular here.

### Wiring up the handlers

Now that we have the handlers, let's create the piece that will help us wire up the handlers to the context. All the other frameworks use annotations for this. In most cases, there is a "base" url ( eg. "/user" in case of GET "/user/{id}", POST "/user", PATCH "/user/{id}", and the list endpoint GET "/user" ). All these are normally handled by the same set of database or template resolvers. Or, at the very least, grouped together in the code base. So, for the sake of our usage, we will write a `HttpContextHelper` that will help us wire up the above two.

```
public class HttpContextHelper { public interface AssociateEndpoint { void associate(String endpoint, HttpHandler handler); } public static void initializeDummyEndpoints(AssociateEndpoint endpointContext) { endpointContext.associate("/", new RootHandler()); endpointContext.associate("/ping", new PingHandler()); }}
```

### The dependency injection

We would like to test our Associations, and ensure that the endpoints are "unique". Also, want to test the response from each handler for its response, and OutputStream handling. So, using the Dependency Injection here helps. We will address the test cases for this in the next post.

Notice that the dependency has been defined as a Functional Interface, thus eliminating any dependencies on HttpServer itself. Yes, I know that the HttpHandler is still a dependency. But, as I see above, the HttpExchange is a really basic data construct. It exposes the OutputStream directly. So, there must be some space to encapsulate boilerplate here.

### The Server itself

```
    public static void main( String[] args ) throws IOException { var httpPort = Integer.getInteger("PORT", 8080); // use HttpServer to build a "ping" endpoing var server = HttpServer.create(new InetSocketAddress(httpPort), 0); HttpContextHelper.initializeDummyEndpoints(server::createContext); server.start(); System.out.println( "Server started on port %d !!".formatted(httpPort) ); }
```

Lets look at the important pieces of this code:

`httpPort` is either taken from the System variable `PORT`, or the default is used.

The `IOException` is thrown, and its not a good practice. It should be carefully logged. We do not have the logging infrastructure setup yet, so maybe we will come back to this, if there is need. For now, this suffices.

Notice the usage of the `HttpContextHelper` here. The `server::createContext` function is passed as a parameter. A great Functional dependency injection.

Some nice Java touches:

- use of the `getInteger` function

- Use of the `formatted` function for string formatting instead of the concat

This project does not use any "preview" features of JDK 21, so no string templates yet.

### The "Benchmark"

Lets look at what we have achieved and the disk usage:

```
ls -l target/sample-1.0-SNAPSHOT.jar -rw-r--r-- 1 vscode vscode 5651 Jan 5 12:04 target/sample-1.0-SNAPSHOT.jar
```

And just so that you know we are not hiding anything, lets run this command:

```
cd targetfind . -type f -printf "%s %p\n"581 ./test-classes/com/example/AppTests.class1124 ./classes/com/example/endpoints/RootHandler.class350 ./classes/com/example/endpoints/HttpContextHelper$AssociateEndpoint.class1115 ./classes/com/example/endpoints/PingHandler.class913 ./classes/com/example/endpoints/HttpContextHelper.class2201 ./classes/com/example/App.class58 ./maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst27 ./maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst130 ./maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst212 ./maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst59 ./maven-archiver/pom.properties5651 ./sample-1.0-SNAPSHOT.jar5243 ./surefire-reports/TEST-com.example.AppTests.xml290 ./surefire-reports/com.example.AppTests.txt
```

These are all the files that are needed to run the application. No other "jars".

### Running the application

To run the application, there are two modes.

Build only the classes:

```
./mvnw compile
```

Run using the classes:

```
java -cp ./target/classes com.example.App
```

Build Jar:

```
./mvnw package
```

Using the Jar:

```
java -cp ./target/sample-1.0-SNAPSHOT.jar com.example.App
```

### Moving On

Now that we have a fully functional Http Server, with no annotations, and a 404 handler, we need to make sure that we can test it. We will ask our AI tools for help. But thats for another day.

### PostScript

Here is the Merge Request for the code mentioned in this article: ( [https://gitlab.com/mandraketech/httpserver-based-todo-app/-/merge\_requests/3/diffs](https://gitlab.com/mandraketech/httpserver-based-todo-app/-/merge_requests/3/diffs) )