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


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

# 文字列で扱う

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

```scala
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`を実装する。

```scala
// 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]`を定義してやればよい。

```scala
val originalHeaderExtract = headerValuePF {
  case h @ OriginalHeader(token) => h
}

val route =
  path("header" / "original") {
    originalHeaderExtract { originalHeader =>
      get {
        complete(s"original => $originalHeader")
      }
    }
  }
```

## headerValueByTypeを使う

`User-Agent`とかと同じようにやっても何故か上手くいかない。

```scala
headerValueByType[`OriginalHeader`]() { originalHeader => ??? }
```

そのため、`headerValueByType`の引数に与えるための`ClassMagnet[OriginalHeader]`を自前で用意する必要がある。
実装例としては以下で、 [ClassMagnet#apply](https://github.com/akka/akka/blob/master/akka-http/src/main/scala/akka/http/scaladsl/server/util/ClassMagnet.scala#L28)とは`extractPF`の実装を変えている。
もちろん、objectの`OriginalHeader`で`ClassMagnet[OriginalHeader]`をmix-inしても良い。

```scala
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`の引数に明示的に与えてやれば良い

```scala
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使っている](https://github.com/akka/akka/blob/master/akka-http/src/main/scala/akka/http/scaladsl/server/directives/HeaderDirectives.scala#L83)ので、`headerValueByType`を使う意味があんまり見えないため、`headerValueByPF`で良さそうかなと。