Try Micronaut
Tags: java, micronaut
Published: 2024-01-03 07:53:05

## Intro
In this time, I will try Micronaut.
I will add a page and uploading files function.

* [Micronaut Framework](https://micronaut.io/)

## Installation and creating a application
To create a Micronaut application, I should install CLI tool.

* [Download - Micronaut Framework](https://micronaut.io/download/)

After installing, I create a simple application.

```
mn create-app micronaut-sample
```

### Adding a Thymeleaf page
Because the created project only has a main class and some property files, I will add a controller class and a Thymeleaf page.

#### build.gradle.kts
```kotlin
plugins {
    id("com.github.johnrengelman.shadow") version "8.1.1"
    id("io.micronaut.application") version "4.2.1"
    id("io.micronaut.aot") version "4.2.1"
}
version = "0.1"
group = "micronaut.sample"
repositories {
    mavenCentral()
}
dependencies {
    annotationProcessor("io.micronaut:micronaut-http-validation")
    annotationProcessor("io.micronaut.serde:micronaut-serde-processor")
    implementation("io.micronaut.serde:micronaut-serde-jackson")

    // Add this
    implementation("io.micronaut.views:micronaut-views-thymeleaf")
    
    compileOnly("io.micronaut:micronaut-http-client")
    runtimeOnly("ch.qos.logback:logback-classic")
    testImplementation("io.micronaut:micronaut-http-client")
}
application {
    mainClass.set("micronaut.sample.Application")
}
java {
    sourceCompatibility = JavaVersion.toVersion("17")
    targetCompatibility = JavaVersion.toVersion("17")
}
graalvmNative.toolchainDetection.set(false)
micronaut {
    runtime("netty")
    testRuntime("junit5")
    processing {
        incremental(true)
        annotations("micronaut.sample.*")
    }
    aot {
        optimizeServiceLoading.set(false)
        convertYamlToJava.set(false)
        precomputeOperations.set(true)
        cacheEnvironment.set(true)
        optimizeClassLoading.set(true)
        deduceEnvironment.set(true)
        optimizeNetty.set(true)
    }
}
```

#### src/main/java/micronaut/sample/pages/PageController.java
```java
package micronaut.sample.pages;

import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.scheduling.annotation.ExecuteOn;
import io.micronaut.views.View;

@Controller("/")
public class PageController {
    @Produces(MediaType.TEXT_HTML)
    @ExecuteOn(TaskExecutors.BLOCKING) 
    @View("uploadFiles")
    @Get("/files")
    public HttpResponse<String> getFilePage() {
        return HttpResponse.ok();
    }
}
```

#### src/main/resources/views/uploadFiles.html
```html
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Uploading File Page</title>
        <meta charset="utf-8">
    </head>
    <body>
        <h1>Hello World!</h1>
        <div>
            <input type="file" id="speadsheet_input">
            <button onclick="UploadFilePage.send()">Send</button>
            <script src="js/uploadFiles.page.js"></script>
        </div>
    </body>
</html>
```

#### ts/uploadFiles.page.ts
```ts
window.UploadFilePage = {
    send() {
        const fileInput = document.getElementById("speadsheet_input") as HTMLInputElement;
        const files = fileInput.files;
        if(files == null || files.length <= 0) {
            return;
        }        
        if(files[0] == null) {
            return;
        }
        const form = new FormData();
        form.append("file", files[0]);
        fetch("http://localhost:8080/files/spreadsheets", {
            method: "POST",
            mode: "cors",
            headers: {
                "fileName": files[0].name
            },
            body: form
        }).then((res) => res.text())
        .then((res) => console.log(res))
        .catch(err => console.error(err));
    },
}
```

* [Micronaut Views](https://micronaut-projects.github.io/micronaut-views/latest/guide/)

### Adding static files
To load JavaScript(TypeScript) files and CSS files, I will add a path to publish static files.

#### src/main/resources/application.properties
```ini
micronaut.application.name=micronaut-sample

# Add this
micronaut.router.static-resources.*.enabled=true
micronaut.router.static-resources.*.paths=classpath:static
```

After adding these two lines, I can access the files in "src/main/resources/static".

* [Expose a WebSocket Server in a Micronaut Application](https://guides.micronaut.io/latest/micronaut-websocket-maven-java.html)

## Receiving and reading files
I can receive "multipart/form-data" as "io.micronaut.http.server.multipart.MultipartBody".

#### src/main/java/micronaut/sample/files/FileController.java
```java
package micronaut.sample.files;

import java.io.IOException;
import java.util.Optional;

import io.micronaut.http.HttpHeaders;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.server.multipart.MultipartBody;

@Controller("/files")
public class FileController {
    @Post(uri = "/spreadsheets", consumes = MediaType.MULTIPART_FORM_DATA, produces = MediaType.TEXT_PLAIN)
    public String uploadSpreadsheet(HttpHeaders headers, @Body MultipartBody file) {
        return "Not implemented";
    }
}
```

* [How to get full list of request headers in Micronaut - StackOverflow](https://stackoverflow.com/questions/62436891/how-to-get-full-list-of-request-headers-in-micronaut)

### Reading received files
"MultipartBody" extends "Publisher< CompletedPart>".
I will add Project Reactor to read received files.

#### build.gradle.kts
```kotlin
...
dependencies {
    annotationProcessor("io.micronaut:micronaut-http-validation")
    annotationProcessor("io.micronaut.serde:micronaut-serde-processor")
    implementation("io.micronaut.serde:micronaut-serde-jackson")
    implementation("io.micronaut.views:micronaut-views-thymeleaf")

    // add this
    implementation("io.projectreactor:reactor-core:3.6.1")

    compileOnly("io.micronaut:micronaut-http-client")
    runtimeOnly("ch.qos.logback:logback-classic")
    testImplementation("io.micronaut:micronaut-http-client")
}
...
```

#### FileController.java
```java
package micronaut.sample.files;

import java.io.IOException;
import java.util.Optional;

import io.micronaut.http.HttpHeaders;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.server.multipart.MultipartBody;
import reactor.core.publisher.Mono;

@Controller("/files")
public class FileController {
    @Post(uri = "/spreadsheets", consumes = MediaType.MULTIPART_FORM_DATA, produces = MediaType.TEXT_PLAIN)
    public Mono<String> uploadSpreadsheet(HttpHeaders headers, @Body MultipartBody file) {
        return Mono.from(file).map(f -> {
                try {
                    byte[] fileData = f.getBytes();
                    Optional<String> fn = headers.findFirst("fileName");
                    String fileName = "";
                    if(fn.isPresent()) {
                        fileName = fn.get();
                    }
                    return "File: " + fileName + " Len: " + fileData.length;
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return "";
            });
    }
}
```

* [Reactor 3 Reference Guide](https://projectreactor.io/docs/core/release/reference/)
* [reactor/reactor-core - GitHub](https://github.com/reactor/reactor-core)

### Receiving over 1MB files
By default, the application only can receive less than 1MB files.
To change the limitation, I add "micronaut.server.multipart.max-file-size" into application.properties.


#### src/main/resources/application.properties
```ini
micronaut.application.name=micronaut-sample
micronaut.router.static-resources.*.enabled=true
micronaut.router.static-resources.*.paths=classpath:static

# add this
micronaut.server.multipart.max-file-size=20971520
```

* [Use the Micronaut Object Storage API to store files in Amazon S3](https://guides.micronaut.io/latest/micronaut-object-storage-aws-gradle-java.html)