Embedded Jetty server with handlers for legacy Java applications
Tags: jetty, java, maven, embedded
Published: 2023-09-07 14:16:49

---
title: Embedded Jetty server with handlers for legacy Java applications
published: true
date: 2023-09-07 00:00:00 UTC
tags: jetty, java, maven, embedded
canonical_url: https://shubham.codes/blog/2023-09-02-embedded-jetty-handlers/
---

## Introduction

[Jetty](https://eclipse.dev/jetty/) is a very powerful yet lightweight `Java` library since ages that helps you create servers and clients for HTTP (literally all the versions), Web Sockets, OSGI, JMX, JAAS and much more.

This post will deal with embedding a Jetty server in a Java application and adding handlers for different requests. I'm assuming you have a basic understanding of Java and Maven and have familiarity with different HTTP methods like GET, POST, PUT, DELETE, etc.

## Jetty Server Architecture

An incoming request is handled using 4 components - Threadpool, Connectors, Handlers and Server.`Server` is the core of the system that handles the management of servers and the entire lifecycle of the server.`Connectors` help in accepting various requests over different protocols like HTTP, HTTPS, etc.`Handlers` are the components that process the incoming request.`Threadpools` are like tiny workers that make the system multi-threaded and help in handling multiple requests at the same time.

An incoming request is first accepted at the `Connector`. Then it is sent to the `Server` which connects it to the `Handler` where the main response is generated. And the response is generated using a particular thread as per `Threadpool`.

## Using Jetty in a Java application

### New Java App

First, let's create a new `Java` app using `Maven`.

```
> mvn archetype:generate \
-DgroupId = com.schwiftycold.poc \
-DartifactId = poc_jetty_server \
-DarchetypeArtifactId = maven-archetype-quickstart \
-DinteractiveMode = false

```

Add the following properties to your `POM` file. This will set the `Java` version and language encoding for the app.

```
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>20</maven.compiler.source>
    <maven.compiler.target>20</maven.compiler.target>
  </properties>

```

### Add Jetty dependency

Add the following dependency in your `pom.xml` file.

```
<dependencies>
  <dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-server</artifactId>
    <version>11.0.3</version>
  </dependency>
  ...
</dependencies>

```

### Main server

The main component of the system is the server class. This class will create a server context and register connectors and handlers. Let's call this class `MainServer`.

#### Threadpool

There are different kinds of Threadpool offered by Jetty like `QueuedThreadPool`, `ExecutorThreadPool`, `ScheduledThreadPool`, etc.

`QueuedThreadPool` maintains a fixed number of threads and a queue to manage incoming requests. When a request comes and the thread is busy then it is added to the queue and will be picked when a thread is available. And, due to its nature, this is widely used for handling `http` requests.

`ScheduledThreadPool` extends `QueuedThreadPool` to handle scheduled tasks. It provides us with a scheduler to execute tasks at a certain interval. It is generally used for scheduling background tasks.

`ExecutorThreadPool` enables you to use custom `Executor` as the Threadpool for Jetty.

Here, we will be using `QueuedThreadPool`.

```
ThreadPool threadPool = new QueuedThreadPool();

```

#### Server

The server is the essential component that manages the connectors and handlers. It takes the Threadpool to create a new server instance.

```
Server server = new Server(threadPool);

```

#### Connector

A connector allows us to accept a variety of different protocols like `HTTP`, `HTTPS`, `Unix domain socket`, etc.

##### HTTP

A simple `HTTP` connector can be initialized using the `ServerConnector` class. And you can change the port to listen to using, `setPort` function. By default, the port is `8080`.

```
ServerConnector connector = new ServerConnector(server);
connector.setPort(9120);
server.setConnectors(new Connector[]{connector});

```

##### HTTPS

For using `HTTPS` you'll need to register the `SSL/TLS` certificate. We won't be using this here but a quick look into this will give us a glance of the infinite possibilities.

First, you will need the sslContextFactory which defines your `SSL/TLS` configurations.

```
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath("/path/to/keystore.jks");
sslContextFactory.setKeyStorePassword("keystore-password");
sslContextFactory.setKeyManagerPassword("key-password");


ServerConnector httpsConnector = new ServerConnector(
    server,
    new SslConnectionFactory(sslContextFactory, "http/1.1")
);
httpsConnector.setPort(8443);
server.setConnectors(new Connector[] {httpsConnector});

```

#### Handlers

Handlers are the ones that will be creating or response. For this, let's create a new singleton class. Let's set this to `null` for now.

```
server.setHandler(null);

```

#### Final code

```
public class MainServer {
  public static void startServer() {
    try {
      ThreadPool threadPool = new QueuedThreadPool();

      Server server = new Server(threadPool);

      ServerConnector connector = new ServerConnector(server);
      connector.setPort(9120);
      
      server.setConnectors(new Connector[]{connector});
      server.setHandler(null);

      server.start();
      server.join();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

```

### Handler class

Handler class is where we will write our logic to process the request. So this requires a special attention.

Let's create a new class that extends `AbstractHandler`

#### Extend AbstractHandler

The `AbstractHandler` required us to implement a `handle` function. This function is triggered on every new request.

The handle method uses four parameters. The `target` parameter denotes the endpoint that was triggered. If we trigger the URL, `http://localhost:9120/hi` then the target will take the value as `/hi`.

`baseRequest` and `request` denote the same thing but in a different context.`baseRequest` is Jetty specific whereas `request` donates the servlet-specific APIs. Here, we won't be using the servlet APIs.

The `response` is the processed result generated by the server which will be sent to the client.

```
public class MainHandler extends AbstractHandler{

  @Override
  public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {
        System.out.println("target: " + target);
        System.out.println("baseRequest: " + baseRequest);
        System.out.println("request: " + request);
        System.out.println("response: " + response);     
  }
}

```

The output of the above code would be something as follows on triggering with `curl`.

```
> curl -X POST "http://localhost:9120/hi" -d "p=value"

target: /hi
baseRequest: Request(POST http://localhost:9120/hi)@57ffc3ca
request: Request(POST http://localhost:9120/hi)@57ffc3ca
response: HTTP/1.1 200

```

Here, you can see the target as `/hi`, the request denotes the method call and reference to the request object and the response is just 200 which denotes a success. We can manipulate this information to create responses based on different inputs.

#### Manage different methods

We can get the request method using the response object by calling the function, `getMethod`.

```
public void handle(...) {
    if ("POST".equalsIgnoreCase(baseRequest.getMethod())) {
      System.out.println("POST request received");
    }
    else if ("GET".equalsIgnoreCase(baseRequest.getMethod())) {
      System.out.println("GET request received");
    }
}

```

Now, calling curl as above will print `POST request received` on the server side.

#### Extract the query parameters

The query parameter and its values can be extracted from the base request object by using the `getParameterNames` method. Here, I'm parsing the values and storing them in a `Map` for accessing them later.

```
if ("POST".equalsIgnoreCase(baseRequest.getMethod())) {
  System.out.println("POST request received");
  Map<String, String> queryParams = new HashMap<>();

  for (Enumeration<String> e = request.getParameterNames(); e.hasMoreElements();) {
    String name = e.nextElement();
    String[] values = request.getParameterValues(name);
    for (String value : values) {
      queryParams.put(name, value);
    }
  }

  System.out.println("Query params: " + queryParams);
  ...

```

Now, you can see the below response on the server side.

```
POST request received
Query params: {p=value}

```

#### Generating the result

The last part is generating the result for specific triggers. This is done by using `getWriter` function to write to the response. You can also set the status of the response using `setStatus` method. You can create more `if` statements for each endpoint.

```
try {
  if (target.startsWith("/hi")) {
    response.setStatus(HttpServletResponse.SC_OK);
    response.getWriter().println("Hello World");
    baseRequest.setHandled(true);
  }
} catch (Exception e) {
  e.printStackTrace();
}

```

#### Final handle method

```
  public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {
    if ("POST".equalsIgnoreCase(baseRequest.getMethod())) {
      System.out.println("POST request received");
      Map<String, String> queryParams = new HashMap<>();

      for (Enumeration<String> e = request.getParameterNames(); e.hasMoreElements();) {
        String name = e.nextElement();
        String[] values = request.getParameterValues(name);
        for (String value : values) {
          queryParams.put(name, value);
        }
      }

      System.out.println("Query params: " + queryParams);

      System.out.println("Target: " +target.startsWith("/hi"));

      try {
        if (target.startsWith("/hi")) {
          System.out.println("Target: " +target);
          response.setStatus(HttpServletResponse.SC_OK);
          response.getWriter().println("Hello World");
          baseRequest.setHandled(true);
        }
      }
      catch (Exception e) {
        e.printStackTrace();
      }

    } else if ("GET".equalsIgnoreCase(baseRequest.getMethod())) {
      System.out.println("GET request received");
    }
  }

```

#### Connect Handler and MainServer

Before testing your new server, you'll also need to add the handler which can be done using the server's `setHandler` method.

```
server.setHandler(new MainHandler());

```

Now calling the `hi` endpoint will return `Hello World` as a response.

```
> curl -X POST "http://localhost:9120/hi" -d "p=value"

Hello World

```

## Conclusion

In this post, we saw how to configure `Jetty` server and set handlers to generate responses for a particular endpoint trigger. This looks like a long post but it revolves around just creating a 'Hello World' server using `Jetty`. You can find the code for the above [here](https://github.com/UnresolvedCold/POC-Jetty-Server-Handlers).