[HttpServer Series] Router - improved - more tests
Tags: java, framewroks, learning
Published: 2024-01-24 20:25:16

---
title: [HttpServer Series] Router - improved - more tests
published: true
date: 2024-01-24 10:17:00 UTC
tags: java, framewroks, learning
canonical_url: https://blog.mandraketech.in/httpserver-series-router-improved-more-tests
series: Project No-Web-Framework-No-Annotations using Java
---

As I started working on the Static file server piece of the http server project, I realised that I did not have enough test cases for the Router implemented in the [last article](https://blog.mandraketech.in/httpserver-series-the-http-router-from-scratch).

So, I started adding more tests. Specifically around the "longest prefix". As I did that, the following cases started failing:

- Route added for `/ping` , and a request for `/pingping` is received

- Route added for `/api` , and request received for `/api/user`

So, once the tests were added and failing, there was scope to refactor the router lookup code to do better. I asked Cody AI: "Can this be done better ?", and it suggested that I use a `TreeMap` and a `floorKey` to get the longest key match. That was a good hint for me to pick up the rest of the pieces. Also, while I was there, I saw that there were a few interfaces, that could be replaced with equivalents from the `java.util.function` package. This allowed the Router to be a little more "functional" in its style.

But first, time to get rid of the `RouteBuilder` class. The `createRouter` was replaced with the following:

```
public static final Router createRouter(Consumer<RouteManager> routeConfig) { var routes = new TreeMap<String, Function<RouteRequest, RouteResponse>>(); routeConfig.accept(new RouteManager() { @Override public RouteManager addHandler(String path, Function<RouteRequest, RouteResponse> handler) { if (routes.containsKey(path)) { throw new IllegalArgumentException("Endpoint already registered: %s".formatted(path)); } routes.put(path, handler); return this; } }); return new Router(routes); }
```

Here are the key changes from the previous implementation:

- The `RouteManager` interface is gone. So no more dependencies for that in the caller of the `addHandler`

- More importantly, `addHandler` is now a fluent style api. It returns a reference to itself, so the caller code can be written with fewer breaks. So the router initializer can be written such.

The functionality for finding the relevant handler has been fixed to take care of:

- "leaf" endpoints registered with a handler

- "node" endpoints registered with a handler

```
    private Function<RouteRequest, RouteResponse> findHandlerForReq(String path) { Function<RouteRequest, RouteResponse> handler = null; String routeKey = routes.floorKey(path); if (routeKey == null) { return null; } if (RouteHelpers.isRoutePrefx(routeKey, path) || path.equals(routeKey)) { handler = routes.get(routeKey); } return handler; } static boolean isRoutePrefx(String prefixKey, String path) { return prefixKey.endsWith("/") && path.startsWith(prefixKey); }
```

The test cases are now "fixed". The importance of designing test cases with completeness. And I have been negligent. But, that is fine. This is a learning project, and I am using my assistants ChatGPT, and Cody AI to give me the tests. And sometimes they will miss things. This time, when I asked them for additional rest cases, they covered the nested cases better.

Also, in this process, the Router has now been made independent of the `HttpHandler`'s `handle` function. `handle` looks like this now:

```
    public void handle(HttpExchange exchange) throws IOException { RouteRequest req = RouteHelpers.createRouteRequest(exchange); var response = processRequest(req); // send the response, and clean up sendResponse(exchange, response); }
```

So, all the testing can happen against the `processRequest` method.

There is also use of a new pattern in this code, for defining helper, or utility, functions that are private to this class. I have used an `interface RouteHelpers` which has static implementations of functions that do not need any state from the router. This keeps my `Router` clear of any private methods that do not need to be there. My attempt at being more functional. Don't know if this is a good pattern or not, but certainly helps when I ask Cody AI to help with writing tests. Less for the assistant to process.

The `RouterTest` also uses a similar pattern to create a functional approach. With the possibility of making the `Helper` a more widely used interface at a later point, if needed.

The `Router`'s handler is now a `Function<RouteRequest, RouteResponse>` type. I can already see that there will be challenges in this function signature, when using chaining handlers. But, I am going to leave that until later. Fight it when we get there, and, more importantly, have a use case for it. My guess is that the functional style of programming may allow us to compose chains of functionality easily. Until then, this is a one works.

Until next time.

The Merge request for the tests added, and the code changes can be found [here on the Gitlab Project](https://gitlab.com/mandraketech/httpserver-based-todo-app/-/merge_requests/8).