mike-neckのブログ

Java or Groovy or Swift or Golang

Spring Web Flux を少しさわってみただけの話

Spring 5 から Reactive Web というのになるらしく、サーバーの性能が落ちにくくなると聞いた(違うかもしれない)ので、 家で試してみることにした。

環境

  • Spring Boot: 2.0.0.RC1
  • Java: 1.8

ビルドファイル

以下の curl コマンドで取得できるもの

curl https://start.spring.io/build.gradle -d dependencies=security,webflux -d bootVersion=2.0.0.RC1

なお、依存ライブラリーは次のようになっている

dependencies {
  compile('org.springframework.boot:spring-boot-starter-security')
  compile('org.springframework.boot:spring-boot-starter-webflux')
  testCompile('org.springframework.boot:spring-boot-starter-test')
  testCompile('io.projectreactor:reactor-test')
  testCompile('org.springframework.security:spring-security-test')
}

ハンドラーファンクション

リクエストをさばくために、 org.springframework.web.reactive.function.server.ServerRequest を受け取って、 reactor.core.publisher.Mono または reactor.core.publisher.Flux を返す関数、または メソッドを作る

// クラス名は MyHandler
Mono<ServerResponse> hello(final ServerRequest serverRequest) {
  return ServerResponse.ok()
      .contentType(MediaType.APPLICATION_JSON)
      .body(
          Mono.just(new Message("hello", OffsetDateTime.now(ZoneId.of("Z")))),
          Message.class);
}

なお、jsonマッピングするクラスは次のようなクラス

@Value
static class Message {
  final String text;
  final OffsetDateTime time;
}

ルーターファンクション

ハンドラーファンクションに対して、URLをマッピングする、ルーターファンクション(org.springframework.web.reactive.function.server.RouterFunction)をBean登録する

// クラス名は MyConfig
@Bean
RouterFunction<ServerResponse> routerFunction(
    final SpringWebfluxDemoApplication springWebfluxDemoApplication) {
  return route(GET("/hello"), myHandler::hello);
}

なお、このメソッドのある MyConfig クラスには @org.springframework.web.reactive.config.EnableWebFlux アノテーションをつける

@EnableWebFlux
@Configuration
public class MyConfig {
  // 省略
}

セキュリティ

元々セキュリティをつけようとは思っていなかったのだが、間違えてつけてしまったので、その設定もしてみる

Reactive web ではないアプリケーションの場合は UserDetailsService を Bean 登録していたが、 Reactive web であるアプリケーションの場合には ReactiveUserDetailsService を Bean 登録する

ドキュメントを読んでいると、 MapReactiveUserDetailsService なるクラスがあるので、そちらを利用する

@Bean
ReactiveUserDetailsService userDetailsService() {
  final UserDetails userDetails = User.withDefaultPasswordEncoder()
      .username("foo")
      .password("bar")
      .roles("BAZ", "QUX")
      .build();
  return new MapReactiveUserDetailsService(userDetails);
}

いつもの

あとはいつものとおり、 @SpringBootApplication アノテーションをつけたクラスと、 main メソッドを作る

@SpringBootApplication
public class App {
  public static void main(String... args) {
    SpringApplication.run(App.class, args);
  }
}

動かしてみた

アプリケーションを起動して curl でアクセスしてみる

f:id:mike_neck:20180208030819p:plain

$ curl -v http://localhost:8080/hello -H "Authorization: Basic $(echo -n 'foo:bar' | openssl base64)"
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /hello HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
> Authorization: Basic Zm9vOmJhcg==
> 
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: application/json
< 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
< 
* Connection #0 to host localhost left intact
{"text":"hello","time":1518024505.503000000}

まあ、なんか、動いた


OffsetDateTime のところがタイムスタンプになっていたので Jackson のカスタマイズ方法をドキュメントから探して CodecCustomizer を Bean 登録すればよいことがわかったのだが、これはうまくいかなかった…

@Bean
CodecCustomizer addJacksonJsr310Support() {
  return configurer -> configurer.customCodecs()
      .encoder(new Jackson2JsonEncoder(objectMapper(), MimeTypeUtils.APPLICATION_JSON));
}

おわり