標準で用意されていない独自のヘッダーを定義して使いたいケース。
大雑把に、文字列として扱う場合と型で扱う場合に分ける
文字列で扱う
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
で良さそうかなと。