Developing and Using the jrest2 Library for HTTP/HTTPS Protocols
Tags: java, opensource, api, rest
Published: 2024-06-25 09:49:50

## Introduction
Hello everyone! Today, I want to introduce you to my pet project - the jrest2 library, which provides a complete implementation of HTTP/HTTPS protocols from scratch. This library is already available on GitHub and published on jitpack.io with released versions. In this post, I will talk about the features of jrest2, its usage, and how you can get started with it.

## What is jrest2?
This library implements a complete HTTP/HTTPS protocol from scratch. It currently supports the following versions of the HTTP protocol:


| VERSION    | SUPPORTED       |
|------------|-----------------|
| *HTTP/1.0* | ✅ Supported     |
| *HTTP/1.1* | ✅ Supported     |
| *HTTP/2*   | ⛔ Not Supported |
| *HTTP/3*   | ⛔ Not Supported |

On top of a completely self-contained protocol implementation is built a layered API structure with different configurations and ways of initializing and applying data to the connection flow.

Also, this library implements the ability to apply and read SSL certificates for both the client part of the connection (read/write) and the server part of the connection.

[![JitPack](https://jitpack.io/v/MikhailSterkhov/jrest2.svg)](https://jitpack.io/#MikhailSterkhov/jrest2)

To use the data library in your project, you need to prescribe a dependency.
Below is an example of how to use the dependency for different build systems:

### Maven

Dependency block for Maven structure project:

```xml
<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>
```

```xml
<dependency>
    <groupId>com.github.MikhailSterkhov</groupId>
    <artifactId>jrest2</artifactId>
    <version>${jrest.version}</version>
</dependency>
```

### Gradle

Dependency block for Gradle structure project:

```groovy
repositories {
	mavenCentral()
	maven { url 'https://jitpack.io' }
}
```

```groovy
compileOnly 'com.github.MikhailSterkhov:jrest2:${jrest.version}'
```

## Features and Advantages of jrest2
* Complete implementation of HTTP/HTTPS protocols
* **Binary executable client files**
* Support for SSL certificates for secure connections
* Layered API structure for flexible configuration and usage
* Easy integration and use

## Getting Started
* **Installation**: Install the library using jitpack.io. Installation instructions can be found in the GitHub repository.
* **Documentation**: Refer to the documentation in the README file of the repository.
* **Usage Examples**: Explore usage examples to understand how to get started with the library.

## WIKI

Here are some examples of how to use the functionality of the top-level API to interact with the HTTP protocol

### CLIENTS

Let's start with the client part of the connection.

First of all, it is necessary to determine what type of channel we will work with and what we need.
For this purpose, the client factory is implemented `com.jrest.http.client.HttpClients`:

```java
// variants of Sockets implementation:
HttpClients.createSocketClient(ExecutorService);
HttpClients.createSocketClient(ExecutorService, boolean keepAlive);
HttpClients.createSocketClient(ExecutorService, int connectTimeout);
HttpClients.createSocketClient(ExecutorService, int connectTimeout, boolean keepAlive);
HttpClients.createSocketClient();
HttpClients.createSocketClient(boolean keepAlive);
HttpClients.createSocketClient(int connectTimeout);
HttpClients.createSocketClient(int connectTimeout, boolean keepAlive);

// variants of HttpURLConnection implementation:
HttpClients.createClient(ExecutorService);
HttpClients.createClient(ExecutorService, int connectTimeout);
HttpClients.createClient(ExecutorService, int connectTimeout, int readTimeout);
HttpClients.createClient();

// variants of binary http-client wrappers implementation:
HttpClients.binary(HttpClient httpClient, Reader reader);
HttpClients.binary(HttpClient httpClient, InputStream inputStream);
HttpClients.binary(HttpClient httpClient, File file) throws IOException;
HttpClients.binary(HttpClient httpClient, Path path) throws IOException;
```

Suppose we decide to implement a Socket connection with the ability 
to make requests asynchronously, and we set its `connect-timeout = 3000ms`, 
and `keep-alive = false` to automatically close the socket after the request is executed.

Example:

```java
HttpClient httpClient = HttpClients.createSocketClient(
        Executors.newCachedThreadPool(), connectTimeout, keepAlive);
```

Next, we can already call any of more than a hundred functions 
to fulfill the request and get an instant response.

For example, send a GET request to the public web page `https://catfact.ninja/fact`, 
from where we will get the result as JSON with a random fact about cats :D

Example:

```java
httpClient.executeGet("https://catfact.ninja/fact")
        .ifPresent(response -> {
            
            HttpProtocol protocol = response.getProtocol(); // HTTP/1.1
            String statusLine = response.getHeaders().getFirst(null); // HTTP/1.1 200 OK

            ResponseCode responseCode = response.getCode();
            
            if (!responseCode.isSuccessful()) {
                throw new RuntimeException("Content not found - " + responseCode);
            }

            System.out.println(httpResponse.getContent().getText());
            // {"fact":"A cat usually has about 12 whiskers on each side of its face.","length":61}
        });
```

The client API also implements one cool thing, thanks to which 
you can simplify the implementation of HTTP requests as much 
as possible by writing just a few words in the code to do it!

### BINARY FILES

Basic information you need to know when writing a binary:

The first lines are general Properties that can be applied in the queries themselves.
The most important among them is the `host = ...` line.
It is mandatory in application, and indicates the main address part of the URL that will be accessed.

Next after Properties are the functions.
Their structure is described by the following signature:

```shell
<name>: <METHOD> /<URI> {
  ...
}
```

The content of the function is divided into several keywords 
that can be used within the body of the function:

- **head**: One of the headings of the query
- **attr**: URI attributes that will be appended to the URL with a '?' (e.g. /employee?id=1, where 'id' is an attribute)
- **body**: Request body
 - **length**: The size of the body to be sent under the guise of the 'Content-Length' header
 - **type**: The body type that will be sent under the 'Content-Type' header appearance
 - **text**: Header content as Hyper text

The values that come after the keyword are mostly 
in the Properties format.

Example binary (`/catfacts.restbin`):

```shell
host = https://catfact.ninja/
randomCatFact = A cat usually has about 12 whiskers on each side of its face.
userAgent = JRest-Binary
contentType = application/json

getFact: GET /fact {
    head User-Agent = ${userAgent}
    head Accept = text/plain
    attr length = 50
}

createFact: POST /fact {
    head User-Agent = ${userAgent}
    body {
        type = ${contentType}
        text = {"fact": "${randomCatFact}", "length": 61}
    }
}
```

After successfully writing our binary, we can start executing it by first creating a BinaryHttpClient 
via the factory: `HttpClients.createBinaryClient(HttpClient, <path-to-binary>)`

BinaryHttpClient has 2 additional methods that distinguish 
it from other HTTP clients: `executeBinary(name)` and `executeBinaryAsync(name)`.

Example (_Java Client_):

```java
BinaryHttpClient httpClient = HttpClients.binary(
        HttpClients.createClient(),
        getClass().getResourceAsStream("/catfacts.restbin"));

httpClient.executeBinary("getFact")
        .ifPresent(httpResponse -> {

                HttpProtocol protocol = response.getProtocol(); // HTTP/1.1
                String statusLine = response.getHeaders().getFirst(null); // HTTP/1.1 200 OK

                ResponseCode responseCode = response.getCode();
            
                if (!responseCode.isSuccessful()) {
                    throw new RuntimeException("Content not found - " + responseCode);
                }

                System.out.println(httpResponse.getContent().getText());
                // {"fact":"A cat usually has about 12 whiskers on each side of its face.","length":61}
        });
```

And also for executing binary functions you can use input properties to 
customize the request from the outside.

Here is an example.

Example (_binary with inputs_):

```shell
host = http://localhost:8080/

get_employee: GET /employee {
    attr id = ${input.employee_id}
}

post_employee: POST /employee {
    body {
        text = ${input.employee}
    }
}
```

Here we can notice the `${input.employee_id}` property, we expect 
to get it from the client.
Below I will give an example of applying it to an executable file.

Example (_Java Client_):

```java
BinaryHttpClient httpClient = HttpClients.binary(
        HttpClients.createClient(),
        HttpClientBinaryUrlTest.class.getResourceAsStream("/employee.restbin"));

httpClient.executeBinary("get_employee",
                Attributes.newAttributes().with("employee_id", 567))
        .ifPresent(httpResponse -> {
            
            System.out.println(httpResponse.getContent().getText());
            // {"id":567,"firstName":"Piter","lastName":"Harrison","jobInfo":{"company":"Microsoft Corporation","website":"https://www.microsoft.com/","profession":"Developer C#","salary":3500}}
        });
```

### SERVERS

To create a server and initialize it, things are a bit more complicated, 
but only because it is a server, and it needs full business logic.

Let's start with the simplest creation of the server as an object, 
form it from the parameters we need:

Example:

```java
HttpServer httpServer = HttpServer.builder()
        .build();
```

Several components are required to properly initialize the server, 
each of which affects a specific part of the software part:


| PARAMETER TYPE    | USAGE EXAMPLE                                       | DESCRIPTION                                                                                                                          |
|-------------------|-----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| InetSocketAddress | `.socketAddress(new InetSocketAddress(80))`         | *Server bindings address and port.*                                                                                                  |
| ExecutorService   | `.executorService(Executors.newCachedThreadPool())` | *Service to execute threads, if not specified, a cached thread pool is used. (CachedThreadPool is used by default if null is specified)* |
| HttpProtocol      | `.protocol(HttpProtocol.HTTP_1_0)`                  | *HTTP protocol, by default HTTP/1.1.*                                                                                                |
| SslContent        | `.ssl(SslContent.builder()...)`                     | *SSL settings for HTTPS, if null, HTTP is used.*                                                                                     |
| HttpListener      | `.notFoundListener(httpRequest -> ...)`             | *Listener to handle requests that have not found an appropriate handler. (404 Not Found)*                                            |

Now based on this information let's try to implement a server 
that supports HTTP/1.1 protocol without SSL certificates

Example;

```java
HttpServer httpServer = HttpServer.builder()
        .socketAddress(new InetSocketAddress(8080))
        .build();

httpServer.bind();
```

We can now intercept requests that come to us by skipping or sending 
back some kind of response. Request listeners can be either asynchronous 
or synchronous.

Examples:

```java
httpServer.registerListener(httpRequest -> {
    
    System.out.println(httpRequest);
    return HttpResponse.ok();
});
```

```java
httpServer.registerAsyncListener("/employee", httpRequest -> 
        HttpResponse.ok(Content.fromEntity(
                Employee.builder()
                        .id(567))
                        .jobInfo(EmployeeJob.builder()
                                .company("Microsoft Corporation")
                                .website("https://www.microsoft.com/")
                                .profession("Developer C#")
                                .salary(3500)
                                .build())
                        .firstName("Piter")
                        .lastName("Harrison")
                        .build())));
```

---

Realizing perfectly well that handling each such request in the form of 
registering them through listeners would not be entirely convenient, 
especially in the case where there may be quite a few endpoints.

Therefore, the MVC module was implemented, providing a more flexible 
and readable implementation of HTTP requests interception.

For the example, let's create an instance that will be a repository 
of HTTP requests for our server and register it:

```java
@HttpServer
public class EmployeesHttpRepository {
}
```

```java
HttpServer httpServer = HttpServer.builder()
        .socketAddress(new InetSocketAddress(8080))
        .build();

httpServer.registerRepository(new EmployeesHttpRepository()); // <----
        
httpServer.bind();
```

**Now we can proceed to the nuances of its further construction, 
because this is where the most interesting things begin!**

To implement some endpoint, we have several annotations that 
allow us to do so:

- **@HttpRequestMapping**
- **@HttpGet**
- **@HttpPost**
- **@HttpDelete**
- **@HttpPut**
- **@HttpPatch**
- **@HttpConnect**
- **@HttpHead**
- **@HttpOptions**
- **@HttpTrace**

Let's start with the simplest one and implement the processing 
of **GET** request to the path **/employee** with the possibility of 
specifying the identifier of the Employee we need 
through attributes (for example, **/employee?id=567**)

Example:

```java
@HttpServer
public class EmployeesHttpRepository {

    @HttpGet("/employee")
    public HttpResponse getEmployee(HttpRequest request) {
        Attributes attributes = request.getAttributes();
        Optional<Integer> attributeIdOptional = attributes.getInteger("id");

        if (!attributeIdOptional.isPresent()) {
            return ...;
        }
        return HttpResponse.ok(Content.fromEntity(
                Employee.builder()
                        .id(attributeIdOptional.get())
                        .jobInfo(EmployeeJob.builder()
                                .company("Microsoft Corporation")
                                .website("https://www.microsoft.com/")
                                .profession("Developer C#")
                                .salary(3500)
                                .build())
                        .firstName("Piter")
                        .lastName("Harrison")
                        .build()));
    }
}
```

Now, suppose in the line where we check for the passed 
attribute `!attributeIdOptional.isPresent()` we need to pass 
the processing of this request to the **NotFoundListener** that 
was specified **when HttpServer** was initialized.

To do this, we need to return the `HttpListener.SKIP_ACTION` constant:

```java
if (!attributeIdOptional.isPresent()) {
    return HttpListener.SKIP_ACTION;
}
```

But in this case it would be more correct to return 
a `400 Bad Request` error, and for this we can call 
the function from HttpResponse in one of two ways:

```java
if (!attributeIdOptional.isPresent()) {
    return HttpResponse.builder()
                .code(ResponseCode.BAD_REQUEST)
                .build();
}
```

or just:

```java
if (!attributeIdOptional.isPresent()) {
    return HttpResponse.badRequest();
}
```

---

Now when we query the `http://localhost:8080/employee?id=567` 
page, we get the following result:

![browse](https://raw.githubusercontent.com/MikhailSterkhov/jrest2/main/.assets/browse.png)

---

### ADDITIONAL HTTP-SERVER FEATURES

**But that's not all!**

The HTTP server repository has several other features that add 
some flexibility and convenience in exceptional cases of library use.

Let's go through some of them!

---

**Annotation @HttpBeforeExecution**:

Annotation allows you to pre-validate an incoming request, 
change some parameters, or perform additional processes before 
processing:

Example:

```java
@HttpBeforeExecution
public void before(HttpRequest httpRequest) {
    httpRequest.setHeaders(
            httpRequest.getHeaders()
                    .set(Headers.Def.USER_AGENT, "Mikhail Sterkhov")
    );
}
```

---

**Annotation @HttpAsync**:

You can hang this annotation on literally any method that handles queries.

It implements some kind of wrapper of the handler in separate threads, 
if it is really necessary for the implementation.

Example:

```java
@HttpAsync
@HttpPatch("/employee")
public HttpResponse patchEmployee(HttpRequest request) {
    Employee employee = request.getContent().toEntity(Employee.class);
    try {
        employeesService.patch(employee);
        return HttpResponse.ok();
    } 
    catch (EmployeeException exception) {
        return HttpResponse.internalError();
    }
}
```

---

**Annotation @HttpAuthenticator**:

To verify requests via authorization, you can use a fully dedicated 
functionality for this purpose, which provides you with an entire 
model API to implement HTTP request authentication.

For examples:

**Basic**

```java
private static final Token.UsernameAndPassword APPROVAL_TOKEN =
            Token.UsernameAndPassword.of("jrest_admin", "password");

@HttpAuthenticator
public ApprovalResult approveAuth(UnapprovedRequest request) {
    return request.basicAuthenticate(APPROVAL_TOKEN);
}
```

**Bearer**

```java
private static final String GENERATED_API_TOKEN = TokenGenerator.defaults(30).generate();

@HttpAuthenticator
public ApprovalResult approveAuth(UnapprovedRequest request) {
    return request.bearerAuthenticate(GENERATED_API_TOKEN);
}
```

**Bearer** (custom)

```java
private static final String GENERATED_API_TOKEN = TokenGenerator.defaults(30).generate();

@HttpAuthenticator
public ApprovalResult approveAuth(UnapprovedRequest request) {
    if (request.getAuthentication() != Authentication.BEARER) {
        return ApprovalResult.forbidden();
    }

    HttpCredentials credentials = request.getRequestCredentials();
    Token token = credentials.getToken();

    if (Objects.equals(token.getValue(), GENERATED_API_TOKEN)) {
        return ApprovalResult.approve();
    }
    
    return ApprovalResult.forbidden();
}
```

In case you want to apply authorization 
**without using classes with the @HttpServer annotation**, 
there are ways to do it too, let's look at a few of them:

```java
HttpServer httpServer = ...;
Token.UsernameAndPassword credentials = Token.UsernameAndPassword.of("jrest_admin", "password")
httpServer.addAuthenticator(HttpBasicAuthenticator.of(credentials));
```

```java
HttpServer httpServer = ...;
httpServer.addAuthenticator(HttpBearerAuthenticator.of(
        Arrays.asList(
                "c9636ffe984e41d7b03c1b42d72402210aa9e64f2bedd6064a70416ba5e",
                "f9af04c492c35e468100f9eead215903a67cdc3168fd95d78ca9bd4f9173",
                "fe332dc685090ddbbf1a7569f22ac2bbe0f13644dbcd3f77cbeaf8f86c47"));
```

```java
HttpServer httpServer = ...;
httpServer.addAuthenticator(HttpBearerAuthenticator.single(
        "c9636ffe984e41d7b03c1b42d72402210aa9e64f2bedd6064a70416ba5e"));
```

```java
HttpServer httpServer = ...;
httpServer.addAuthenticator(Authentication.DIGEST, (unapprovedRequest) -> { return ApprovalResult.forbidden(); });
```

---

**Annotation @HttpNotAuthorized**:

With this annotation, you can mark methods with HTTP requests 
to be excluded from the request authentication process.

Example:

```java
@HttpNotAuthorized
@HttpGet("/employee")
public HttpResponse doGet(HttpRequest request) {
    // request handle logic...
}
```

## Conclusion
I hope the jrest2 library will be useful for your project. If you have any questions or suggestions, feel free to open an issue in the repository or contact me directly. Your feedback and contributions are always welcome!

[Link to the jrest2 repository on GitHub](https://github.com/MikhailSterkhov/jrest2)