DEV Community

Masui Masanori
Masui Masanori

Posted on

Try Micronaut

Intro

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

Installation and creating a application

To create a Micronaut application, I should install CLI tool.

After installing, I create a simple application.

mn create-app micronaut-sample
Enter fullscreen mode Exit fullscreen mode

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

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

src/main/java/micronaut/sample/pages/PageController.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();
    }
}
Enter fullscreen mode Exit fullscreen mode

src/main/resources/views/uploadFiles.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>
Enter fullscreen mode Exit fullscreen mode

ts/uploadFiles.page.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));
    },
}
Enter fullscreen mode Exit fullscreen mode

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

micronaut.application.name=micronaut-sample

# Add this
micronaut.router.static-resources.*.enabled=true
micronaut.router.static-resources.*.paths=classpath:static
Enter fullscreen mode Exit fullscreen mode

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

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

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";
    }
}
Enter fullscreen mode Exit fullscreen mode

Reading received files

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

build.gradle.kts

...
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")
}
...
Enter fullscreen mode Exit fullscreen mode

FileController.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 "";
            });
    }
}
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

Top comments (3)

Collapse
 
emmysteven profile image
Emmy Steven

Can you point me to a resource for unit testing for Quarkus? I'll be more than grateful if you do.

Collapse
 
masanori_msl profile image
Masui Masanori

Thank you for reading my post.
I haven't tried Quarkus yet. If I come into contact with Quarkus in the future, I will post about it.

Collapse
 
insouciantqualms profile image
InsouciantQualms

Using it myself and loving the speed, lightweight footprint and easy of integrating with GraalVM, AWS Lambda and gRPC.