Micronaut リクエストバインディング

Micronautのコントローラにて各種データの受け取り方(リクエストバインディング)についてまとめておきます。

公式ドキュメントがありますので、そちらも参考にしてみてください。
[Micronaut -> 6.4 Simple Request Binding](https://docs.micronaut.io/latest/guide/index.html#binding)


## 環境
Micronaut Version: 2.0.0
Java: 11


## リクエストパラメータ

リクエストパラメータを取得する場合はメソッドの引数にパラメータを指定するだけになります。

```java:
@Get("/hello{?name}")
// @Get("/value") // パラメータ名の省略も可能
public String hello(Optional<String> name) {
    return name.orElse("Hi!");
}
```

### リクエストパラメータ名と変数名を変える場合

リクエストパラメータ名とJavaの変数名が異なる場合は、QueryValueを利用します。

```java:
@Get("/hello2{?first_name}")
public String hello2(@QueryValue("first_name") Optional<String> firstName) {
    return firstName.orElse("Hi!");
}
```

```console:
$ curl "http://localhost:8080/queryparam/hello2?first_name=taro"
taro
```

### リクエストパラメータ自体を必須項目とする場合

上記2つのメソッドは、リクエストパラメータを指定していなくてもエラーになりませんでした。
リクエストパラメータの指定がない場合にエラーにするにはOptionalを利用しないことで可能になります。

同時に該当のパラメータの値がNullを許容するかも判断しています。
今回はNullを許容しないため、ControllerにValidatedアノテーションを、メソッドのnameパラメータにNotBlankアノテーションを付与しました。

```java:
@Controller("/queryparam")
@Validated
public class QueryParamController {
    @Get("/hello3")
    public String hello3(@NotBlank String name) {
        return name;
    }
}
```

```console:
$ curl "http://localhost:8080/queryparam/hello3"
{"message":"Required argument [String name] not specified","path":"/name","_links":{"self":{"href":"/queryparam/hello3","templated":false}}}

$ curl "http://localhost:8080/queryparam/hello3?name="
{"message":"name: must not be blank","_links":{"self":{"href":"/queryparam/hello3?name=","templated":false}}}

$ curl "http://localhost:8080/queryparam/hello3?name=Taro"
Taro
```

### リクエストパラメータをBeanにバインディング

リクエストパラメータをBeanにバインディングするには、`{?pojo*}`を使用して、格納したいBean変数にRequestBeanアノテーションを付与することで実現できます。

```java:Controller
@Get("/hello4{?queryParamReq*}")
public String hello4(@NotBlank @RequestBean QueryParamReq queryParamReq) {
    return queryParamReq.getFirstName().orElse("sei") + " " +
           queryParamReq.getLastName().orElse("mei") + " さん";
}
```

BeanにはIntrospectedアノテーションを付与します。

```java:QueryParamReq
@Introspected
public class QueryParamReq {
    @QueryValue("first_name")
    private Optional<String> firstName;
    @QueryValue("last_name")
    private Optional<String> lastName;

    public QueryParamReq(Optional<String> firstName, Optional<String> lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public Optional<String> getFirstName() {
        return this.firstName;
    }

    public Optional<String> getLastName() {
        return this.lastName;
    }
}
```

```console:
$ curl "http://localhost:8080/queryparam/hello4?first_name=suzuki&last_name=taro"
suzuki taro さん
```


## URLパスパラメータ

PathVariableアノテーションを利用することで、URLパスパラメータを指定可能です。

```java:
@Get("/hello/{name}")
public String hello(@PathVariable String name) {
    return name;
}
```


### URLパスパラメータをBeanにバインディング

今度はURLパスパラメータもBeanにバインディングしてみます。

```java:Controller
@Get("/hello2/{first_name}/{last_name}")
public String hello2(@RequestBean PathVariableReq pathVariableReq) {
    return pathVariableReq.getFirstName().orElse("sei") + " " +
           pathVariableReq.getLastName().orElse("mei") + " さん";
}
```

```java:PathVariableReq
@Introspected
public class PathVariableReq {
    @PathVariable("first_name")
    private Optional<String> firstName;
    @PathVariable("last_name")
    private Optional<String> lastName;

    public PathVariableReq(Optional<String> firstName, Optional<String> lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public Optional<String> getFirstName() {
        return this.firstName;
    }

    public Optional<String> getLastName() {
        return this.lastName;
    }
}
```


## クッキーパラメータ

クッキーパラメータはCokieValueアノテーションで取得可能です。


```java:
@Get("/hello")
public String hello(@CookieValue("name") Optional<String> name) {
    return name.orElse("Hi!");
}
```


```console:
$ curl "http://localhost:8080/cookie/hello" -b "name=taro"
taro
```

## リクエストヘッダーパラメータ

リクエストヘッダーを取得するには、Headerアノテーションを付与します。

```java:
@Get("/hello")
public String hello(@Header("User-Agent") String userAgent) {
    return userAgent;
}
```

```console:
$ curl "http://localhost:8080/header/hello" 
curl/7.54.0
```

## リクエストボディ

Bodyの値を取得するにはBodyアノテーションを付与します。

```
@Post(value = "/hello", consumes = MediaType.TEXT_PLAIN)
public String hello(@Body Optional<String> name) {
    return name.orElse("Hi!");
}
```

```console:
$ curl -X POST "http://localhost:8080/body/hello" -d "taro" -H "Content-Type: text/plain"
taro
```

### BodyのjsonをBeanにバインディング

BodyのjsonをBeanにバインディングする場合は、BodyアノテーションをBeanに付与すれば良いです。

```java:Controller
@Post("/hello2")
public String hello2(@Body BodyParamReq bodyParamReq) {
    return bodyParamReq.getFirstName().orElse("sei") + " " +
           bodyParamReq.getLastName().orElse("mei") + " さん";
}
```

BodyのjsonはJacksonを利用して変換しているため、jsonカラム名とBeanフィールド名が異なる場合にはJsonPropertyを利用して変換してあげればいいです。

```java:BodyParamReq
@Introspected
public class BodyParamReq {
    @JsonProperty("first_name")
    private Optional<String> firstName;
    @JsonProperty("last_name")
    private Optional<String> lastName;

    public BodyParamReq(Optional<String> firstName, Optional<String> lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public Optional<String> getFirstName() {
        return this.firstName;
    }

    public Optional<String> getLastName() {
        return this.lastName;
    }
}
```

```console:
$ curl -X POST "http://localhost:8080/body/hello2" -d '{ "first_name": "suzuki", "last_name": "taro" }' -H "Content-Type: application/json"
suzuki taro さん
```


### Bodyのパラメータをそれぞれの変数にバインディング

Body内のパラメータをそれぞれの変数にバインディングするには、Postアノテーションのconsumesに`MediaType.APPLICATION_FORM_URLENCODED`を指定してあげて、Bodyアノテーションにパラメータ名を指定すればバインディングされます。

```java:
@Post(value = "/hello3", consumes = MediaType.APPLICATION_FORM_URLENCODED)
    public String hello3(@Body("first_name") Optional<String> firstName, @Body("last_name") Optional<String> lastName) {
        return firstName.orElse("sei") + " " +
                lastName.orElse("mei") + " さん";
    }
```

```console:
$ curl -X POST "http://localhost:8080/body/hello3" -d "first_name=suzuki" -d "last_name=taro" -H "Content-Type: application/x-www-form-urlencoded"
suzuki taro さん
```

## セッションを利用

セッションを利用する場合は`micronaut-session`を利用します。

```gradle:
implementation("io.micronaut:micronaut-session")
```

```java:Controller
@Post(value = "/hello", consumes = MediaType.APPLICATION_FORM_URLENCODED)
public String setName(Session session, @Body Optional<String> name) {
    if (name.isPresent())
        session.put("name", name.get());
    return name.orElse("Hi!");
}

@Get("/hello")
public String hello(Session session) {
    return session.get("name", String.class).orElse("Hi!");
}
```

```console:
$ curl -v -X POST "http://localhost:8080/session/hello" -d "name=taro" -H "Content-Type: application/x-www-form-urlencoded"
一部省略
< HTTP/1.1 200 OK
< set-cookie: SESSION=NTJhZjY1MDAtMDhmZC00Y2YwLWFmMjctMTMxZjcwMDRhZjBi; Path=/; HTTPOnly
一部省略
taro

$ curl "http://localhost:8080/session/hello" -b "SESSION=NTJhZjY1MDAtMDhmZC00Y2YwLWFmMjcMxZjcwMDRhZjBi"
taro
```

### Sessionの値をメソッド変数に格納する

Sessionの値をそのままメソッド変数に格納するには`SessionValue`アノテーションを利用します。

```java:
@Post(value = "/hello", consumes = MediaType.APPLICATION_FORM_URLENCODED)
public String setName(Session session, @Body Optional<String> name) {
    if (name.isPresent())
        session.put("name", name.get());
    return name.orElse("Hi!");
}

@Get("/hello2")
@SessionValue("name")
public String hello2(@SessionValue Optional<String> name) {
    return name.orElse("Hi!");
}
```

```console:
$ curl -v -X POST "http://localhost:8080/session/hello" -d "name=taro" -H "Content-Type: application/x-www-form-urlencoded"
一部省略
< HTTP/1.1 200 OK
< set-cookie: SESSION=YzYyMjM4YjMtYTczYy00NGRiLTk3MmQtZTNjZmU0YzhhY2Jm; Path=/; HTTPOnly
一部省略
taro

$ curl "http://localhost:8080/session/hello2" -b "SESSION=YzYyMjM4YjMtYTczYy00NGRiLTk3MmQtZTNjZmU0YzhhY2Jm"
taro
```


## 参考
[Micronaut -> 6.4 Simple Request Binding](https://docs.micronaut.io/latest/guide/index.html#binding)
[Spring MVC コントローラの引数](https://qiita.com/MizoguchiKenji/items/2a041f3a3eb13274e55c) 記事の形など参考にさせていただきました。