Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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

文字列で扱う

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした