昨日は、Hello Akka 的なやつをやった。
ので、今日は HTTP のやつを触った。内容が分かってるわけじゃなくて、とりあえず動いたなぁ、っていう感じ。雰囲気は理解した。
Akka HTTP Quickstart for Java · Lightbend Tech Hub
3日で忘れるので、自分用メモ。
まずは動かす
また今日も Records を使おうと思ってるから、Java 15 で動くように build.gradle を少し書き換えてから実行。
ユーザーを追加して:
❯ curl -H "Content-type: application/json" -X POST -d '{"name": "MrX", "age": 31, "countryOfResidence": "Canada"}' http://localhost:8080/users {"description":"User MrX created."} ❯ curl -H "Content-type: application/json" -X POST -d '{"name": "Anonymous", "age": 55, "countryOfResidence": "Iceland"}' http://localhost:8080/users {"description":"User Anonymous created."} ❯ curl -H "Content-type: application/json" -X POST -d '{"name": "Bill", "age": 67, "countryOfResidence": "USA"}' http://localhost:8080/users {"description":"User Bill created."}
追加されたことを確認:
❯ curl -s http://localhost:8080/users {"users":[{"age":31,"name":"MrX","countryOfResidence":"Canada"},{"age":55,"name":"Anonymous","countryOfResidence":"Iceland"},{"age":67,"name":"Bill","countryOfResidence":"USA"}]}
OK。3人登録されてるなー。面白いなー。
じゃ、個別で取得してみよう:
❯ curl -s http://localhost:8080/users/Bill {"empty":false,"present":true}
お。思ってたんと違う。ソースを見てみたら、Optional のフィールドだからだな:
public final static class GetUserResponse { public final Optional<User> maybeUser; public GetUserResponse(Optional<User> maybeUser) { this.maybeUser = maybeUser; } }
ふーん。それはとりあえずおいといて、DELETEもチェックしといた。これはOK。
❯ curl -X DELETE http://localhost:8080/users/Bill {"description":"User Bill deleted."} ❯ curl -s http://localhost:8080/users {"users":[{"age":55,"name":"Anonymous","countryOfResidence":"Iceland"},{"age":31,"name":"MrX","countryOfResidence":"Canada"}]}
Optional を扱えるように
さて。Optional を扱えるようにしようかな。そもそも Optional をフィールドに持ってそれを返すのってありなのかなぁ?とは思うけど、Quickstart だからそのまま進めよう。
Jackson の ObjectMapper に設定を追加すれば良いんだよなぁ。と思いつつ Akka のそれっぽいドキュメントを読んでみる。
Serialization with Jackson • Akka Documentation
ん? Jdk8Module はデフォルトで有効っぽい?
The following Jackson modules are enabled by default:
akka.serialization.jackson { # The Jackson JSON serializer will register these modules. jackson-modules += "akka.serialization.jackson.AkkaJacksonModule" # AkkaTypedJacksonModule optionally included if akka-actor-typed is in classpath jackson-modules += "akka.serialization.jackson.AkkaTypedJacksonModule" # AkkaStreamsModule optionally included if akka-streams is in classpath jackson-modules += "akka.serialization.jackson.AkkaStreamJacksonModule" jackson-modules += "com.fasterxml.jackson.module.paramnames.ParameterNamesModule" jackson-modules += "com.fasterxml.jackson.datatype.jdk8.Jdk8Module" jackson-modules += "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule" jackson-modules += "com.fasterxml.jackson.module.scala.DefaultScalaModule" }
でも、実際は効いてないわけだから・・・。ふーむ。コード見てみるか。
この Quickstart で使ってる ObjectMapper はこれだな?
akka-http/Jackson.java at master · akka/akka-http · GitHub
private static final ObjectMapper defaultObjectMapper = new ObjectMapper().enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
設定が入ってるようには見えないな。
ということは、Akka HTTP と akka-serialization-jackson は関係ないってことか?と思ってうろうろしたら見つけた:
なるほど。akka-serialization-jackson には設定が色々入ってるけど、Akka HTTP はそれとは関係ないってことか。
akka-serialization-jackson の ObjectMapper を使う
なら、Akka HTTP デフォルトの ObjectMapper を使うのをやめて、Jdk8Module を設定したものから Marshaller を作れば良さそう。
// Quickstart ではこっちを使ってる public static <T> Marshaller<T, RequestEntity> marshaller() { return marshaller(defaultObjectMapper); } // こっちを使って設定済みの ObjectMapper を渡せば良さそう public static <T> Marshaller<T, RequestEntity> marshaller(ObjectMapper mapper) { return Marshaller.wrapEntity( u -> toJSON(mapper, u), Marshaller.stringToEntity(), MediaTypes.APPLICATION_JSON ); }
自前で用意するの面倒くさいから、さっきの akka-serialization-jackson から ObjectMapper を取ってこれないかなぁ?と思ったら取れた。でも、これが正しいやり方なのかどうかはよく分かってない。
if (!(system.classicSystem() instanceof ExtendedActorSystem extendedActorSystem)) { throw new IllegalArgumentException("Failed to get object mapper."); } objectMapper = new JacksonObjectMapperProvider(extendedActorSystem).getOrCreate("akka-http", Optional.empty());
使ってる system が akka-actor-typed のもので、キャストできないって怒られたからしばらく悩んで、ぼーっと ActorSystem のメソッドを眺めてたら classicSystem() ってメソッドがあって、これでキャストできた。
ところで、折角なので instanceof のパターンマッチング使ってみた。Records 同様 Java 15 で Second Preview みたいで、Java 16 で正式に導入されるのかなぁ。
JEP 394: Pattern Matching for instanceof
こういうときは if の後ろのブロックでこの変数を使えるっての面白いなー。良い。
https://docs.oracle.com/javase/jp/15/docs/specs/patterns-instanceof-jls.html#jls-6.3.2
はい動いたー。
❯ curl -s http://localhost:8080/users/Bill {"name":"Bill","age":67,"countryOfRecidence":"USA"}
Records に変えてみる
昨日に引き続き、メッセージ用のオブジェクトを Records に変更してみる。けど、HTTP のレスポンスに使ってるオブジェクトもあるから「はて?Jackson は Records に対応しているのだろうか?」と思いつつ実行。
ちなみに IntelliJ IDEA さん、よくできる子やで。
おー。Recordにできるよーって出る。IDEA。空気読めるいい子だ。 pic.twitter.com/Gt4362aYxx
— Mitsuyuki Shiiba (@bufferings) January 24, 2021
static final がつくけど要らないかなぁって思うのでそれだけ消しておいた。
からの実行!
❯ curl -H "Content-type: application/json" -X POST -d '{"name": "Bill", "age": 67, "countryOfResidence": "USA"}' http://localhost:8080/users The request content was malformed: Cannot unmarshal JSON as User
お。JSON の unmarshal ができないって言われた。ふむふむ。Jackson の Records 対応を見てみる。
Jackson 2.12 Most Wanted (5/5):. Support ‘java.lang.Record’ | by @cowtowncoder | Dec, 2020 | Medium
2.12 で対応したみたいだな。Akka HTTP は 2.10 を使ってるみたいだから、2.12 を使うようにしてみるか。
implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.1'
からの再実行:
❯ curl -H "Content-type: application/json" -X POST -d '{"name": "MrX", "age": 31, "countryOfResidence": "Canada"}' http://localhost:8080/users {"description":"User MrX created."} ❯ curl -H "Content-type: application/json" -X POST -d '{"name": "Bill", "age": 67, "countryOfResidence": "USA"}' http://localhost:8080/users {"description":"User Bill created."} ❯ curl -H "Content-type: application/json" -X POST -d '{"name": "Anonymous", "age": 55, "countryOfResidence": "Iceland"}' http://localhost:8080/users {"description":"User Anonymous created."} ❯ curl -s http://localhost:8080/users {"users":[{"name":"MrX","age":31,"countryOfResidence":"Canada"},{"name":"Bill","age":67,"countryOfResidence":"USA"},{"name":"Anonymous","age":55,"countryOfResidence":"Iceland"}]} ❯ curl -s http://localhost:8080/users/Bill {"name":"Bill","age":67,"countryOfResidence":"USA"} ❯ curl -X DELETE http://localhost:8080/users/Bill {"description":"User Bill deleted."} ❯ curl -s http://localhost:8080/users {"users":[{"name":"MrX","age":31,"countryOfResidence":"Canada"},{"name":"Anonymous","age":55,"countryOfResidence":"Iceland"}]}
動いたー。面白かったヽ(=´▽`=)ノ