3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Akka-HTTP]カスタムヘッダーの取り扱い方

Posted at

標準で用意されていない独自のヘッダーを定義して使いたいケース。
大雑把に、文字列として扱う場合と型で扱う場合に分ける

文字列で扱う

headerValueByNameで良い。
引数にSymbolStringを渡せば、それにマッチするヘッダーの値が取り出せる。

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

型で扱う

headerValueByPFheaderValueByTypeのどちらかを使えば出来る。
以下の様なリクエスト/レスポンスになるように実装する。

$ curl localhost:8080/header/original -H 'My-Header: yes'
original => My-Header: token(yes)

まず、カスタムヘッダーとなる型を用意する。
ModeledCustomHeaderModeledCustomHeaderCompanionを実装する。

// 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のOriginalHeaderClassMagnet[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で良さそうかなと。

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?