mike-neckのブログ

JavaかJavaFXかJavaEE(なんかJava8が多め)

Spring WebFlux で Mono が empty の場合に404 を返す

Spring WebFlux フレームワークは戻り値の方は Mono<V> になるのだが、この Mono<V> が empty の場合、Spring 側でよしなにやってくれると思ったら、実はそうでもないらしい。

例えばこのようなハンドラーを作ってみる

  Mono<ServerResponse> test(final ServerRequest request) {
    final Optional<String> value = request.queryParam("value");
    return Mono.justOrEmpty(value)
        .map(Wrapper::new)
        .flatMap(v -> ServerResponse.ok().body(Mono.just(v), Wrapper.class));
  }

  @Bean
  RouterFunction<ServerResponse> testEndpointHandler() {
    return route(GET("/test"), this::test);
  }

  @Value
  public static class Wrapper {
    private final String value;
  }

このハンドラーはクエリーパラメーター value の値を json にラップして返してくれる。

これに対してクエリーパラメーター value に対して値を設定してリクエストを送ってみると次のようなレスポンスが返ってくる。

$ curl -v "http://localhost:8080/test?value=foo"
> GET /test?value=foo HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: application/json;charset=UTF-8
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Content-Type-Options: nosniff
< X-Frame-Options: DENY
< X-XSS-Protection: 1 ; mode=block
< 
{"value":"foo"}

一方、クエリーパラメーター value がないリクエストの場合は次のようになる。

$ curl -v "http://localhost:8080/test"
> GET /test HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Content-Type-Options: nosniff
< X-Frame-Options: DENY
< X-XSS-Protection: 1 ; mode=block
< content-length: 0
< 

クエリーパラメーター value が存在しない場合は返す Mono<ServerResponse> は empty なので 404 が返ってきてほしいのだが、実際は200 が返ってきてしまう。

このような場合は Mono#switchIfEmpty(Mono<? extends V>) を用いる。

Mono (Reactor Core 3.1.5.RELEASE)


修正後のコード(一部)

  Mono<ServerResponse> test(final ServerRequest request) {
    final Optional<String> value = request.queryParam("value");
    return Mono.justOrEmpty(value)
        .map(Wrapper::new)
        .flatMap(v -> ServerResponse.ok().body(Mono.just(v), Wrapper.class))
        .switchIfEmpty(ServerResponse.notFound().build());
  }

修正後のアプリケーションに対してクエリーパラメーター value を設定せずにリクエストしてみる。

$ curl -v http://localhost:8080/test --basic -u foo:foo-pass
> GET /test HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
> 
< HTTP/1.1 404 Not Found
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Content-Type-Options: nosniff
< X-Frame-Options: DENY
< X-XSS-Protection: 1 ; mode=block
< content-length: 0
< 

404が返ってきた。