標準で用意されていない独自のヘッダーを定義して使いたいケース。
大雑把に、文字列として扱う場合と型で扱う場合に分ける
文字列で扱う
headerValueByNameで良い。
引数にSymbolかStringを渡せば、それにマッチするヘッダーの値が取り出せる。
val route =
path("header" / "ping") {
get {
headerValueByName('Message) { msg: String =>
complete(s"pong: $msg")
}
}
}
リクエスト/レスポンスはこのようになる。
非常に簡単で良い。
$ curl 'http://localhost:8080/header/ping' -H 'Message: hello'
pong: hello
型で扱う
headerValueByPFとheaderValueByTypeのどちらかを使えば出来る。
以下の様なリクエスト/レスポンスになるように実装する。
$ curl localhost:8080/header/original -H 'My-Header: yes'
original => My-Header: token(yes)
まず、カスタムヘッダーとなる型を用意する。
ModeledCustomHeaderとModeledCustomHeaderCompanionを実装する。
// class for custom header
class OriginalHeader(token: String) extends ModeledCustomHeader[OriginalHeader] {
override def companion: ModeledCustomHeaderCompanion[OriginalHeader] = OriginalHeader
override def value(): String = s"token($token)"
}
// companion object
object OriginalHeader extends ModeledCustomHeaderCompanion[OriginalHeader] {
override def name: String = "My-Header" // class名と違う
override def parse(value: String): Try[OriginalHeader] = Try(new OriginalHeader(value))
}
クラス名はOriginalHeaderだが、リクエストのヘッダーとしてはMy-Headerで受けることとする。
headerValueByPFを使う
PartialFunction[HttpHeader, T]を定義してやればよい。
val originalHeaderExtract = headerValuePF {
case h @ OriginalHeader(token) => h
}
val route =
path("header" / "original") {
originalHeaderExtract { originalHeader =>
get {
complete(s"original => $originalHeader")
}
}
}
headerValueByTypeを使う
User-Agentとかと同じようにやっても何故か上手くいかない。
headerValueByType[`OriginalHeader`]() { originalHeader => ??? }
そのため、headerValueByTypeの引数に与えるためのClassMagnet[OriginalHeader]を自前で用意する必要がある。
実装例としては以下で、 ClassMagnet#applyとはextractPFの実装を変えている。
もちろん、objectのOriginalHeaderでClassMagnet[OriginalHeader]をmix-inしても良い。
val originalHeaderMagnet = new ClassMagnet[OriginalHeader] {
override def classTag: ClassTag[OriginalHeader] = implicitly[ClassTag[OriginalHeader]]
override def runtimeClass: Class[OriginalHeader] = classOf[OriginalHeader]
override def extractPF: PartialFunction[Any, OriginalHeader] = {
case h: CustomHeader if h.name == OriginalHeader.name => OriginalHeader(h.value())
case h: RawHeader if h.name == OriginalHeader.name => OriginalHeader(h.value)
}
}
このClassMagnet[OriginalHeader]をheaderValueByTypeの引数に明示的に与えてやれば良い
val route =
path("original") {
headerValueByType[OriginalHeader](originalHeaderMagnet) { originalHeader =>
get {
complete(s"original => $originalHeader")
}
}
}
注意点
headerValueByType[T]を使う場合、Tは別のクラスの内部クラスにしてはならない。
Class#getSimpleNameでコケるという意味不明な状況に陥る。
以下にStackTraceを一部抜粋。
java.lang.InternalError: Malformed class name
at java.lang.Class.getSimpleName(Class.java:1322)
at akka.http.scaladsl.server.directives.HeaderDirectives$class.headerValueByType(HeaderDirectives.scala:60)
at akka.http.scaladsl.server.Directives$.headerValueByType(Directives.scala:34)
at net.petitviolet.application.service.RoutingSampleService$Inner$$anonfun$25$$anonfun$apply$39$$anonfun$apply$40.apply(RoutingSampleService.scala:257
)
at net.petitviolet.application.service.RoutingSampleService$Inner$$anonfun$25$$anonfun$apply$39$$anonfun$apply$40.apply(RoutingSampleService.scala:257
)
at akka.http.scaladsl.server.Directive$$anonfun$addByNameNullaryApply$1$$anonfun$apply$13.apply(Directive.scala:136)
at akka.http.scaladsl.server.Directive$$anonfun$addByNameNullaryApply$1$$anonfun$apply$13.apply(Directive.scala:136)
at akka.http.scaladsl.server.directives.BasicDirectives$$anonfun$mapRouteResult$1$$anonfun$apply$3.apply(BasicDirectives.scala:32)
...
一応防ぐ手段もあって、クラス名をOriginalHeaderではなく$OriginalHeaderにすればInternalErrorは起こらなくなる。
型で縛るカスタムヘッダーまとめ
headerValueByType使う場合でもPartialFunction実装していて、かつheaderValueByTypeの中でheaderValueByPF使っているので、headerValueByTypeを使う意味があんまり見えないため、headerValueByPFで良さそうかなと。