akka/akka-http

Custom headers should be parsed to the custom types instead of `RawHeader`

オープン

#1,196 opened on 2017/06/13

 (1 件のコメント) (4 件のリアクション) (0 人の担当者)Scala (598 件のフォーク)batch import
1 - triagedhelp wantedt:coret:model

Repository metrics

Stars
 (1,311 個のスター)
PR merge metrics
 (平均マージ 1d 10h) (30d で 2 merged PRs)

説明

Akka HTTP should allow registering custom headers so that a custom header can be rendered to the custom type instead of RawHeader. Custom methods, mediatypes and status codes are allowed to be passed to ParserSettings.

It should be possible to read a custom header by request.header[ApiTokenHeader]. Please see below a reproducer.

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.headers.{ModeledCustomHeader, ModeledCustomHeaderCompanion, `User-Agent`}
import akka.http.scaladsl.model.{HttpRequest, HttpResponse}
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Flow
import akka.testkit.TestKit
import org.scalatest.{AsyncFlatSpecLike, Matchers}

import scala.concurrent.duration._
import scala.util.Try

class CustomHeaderSpec extends TestKit(ActorSystem("CustomHeaderSpec")) with AsyncFlatSpecLike with Matchers {

  implicit val ac = ActorMaterializer()

  val flow = Flow[HttpRequest].map { request =>
    val userAgent = request.header[`User-Agent`].map(_.value).getOrElse("N/A")
    val apiKey = request.header[ApiTokenHeader].map(_.value).getOrElse("N/A")
    val apiKeyAsRaw =
      request.headers
        .collectFirst { case h if h.lowercaseName == ApiTokenHeader.lowercaseName => h.value }
        .getOrElse("N/A")

    HttpResponse(entity = s"User-Agent: $userAgent apiKey: $apiKey apiKeyAsRaw: $apiKeyAsRaw")
  }

  val binding = Http().bindAndHandle(flow, "localhost", port = 0)

  it should "parse to custom header object" in {

    binding flatMap { b =>
      Http()
        .singleRequest(HttpRequest(uri = s"http://localhost:${b.localAddress.getPort}/")
          .withHeaders(`User-Agent`("abc"), ApiTokenHeader("myToken")))
    } flatMap { response =>
      response.entity.toStrict(30 seconds)
    } flatMap { e =>
      e.data.utf8String shouldBe "User-Agent: abc apiKey: myToken apiKeyAsRaw: myToken"
    }
  }
}

final class ApiTokenHeader(token: String) extends ModeledCustomHeader[ApiTokenHeader] {
  override def renderInRequests = true
  override def renderInResponses = false
  override val companion = ApiTokenHeader
  override def value: String = token
}
object ApiTokenHeader extends ModeledCustomHeaderCompanion[ApiTokenHeader] {
  override val name = "apiKey"
  override def parse(value: String) = Try(new ApiTokenHeader(value))
}

コントリビューターガイド