Sprayからのマイグレーションガイド

Sprayからのマイグレーションガイド

一般的な注意事項

akka-httpに移植されていない機能:

  • ``overrideStatusCode``とも呼ばれる ``respondWithStatus``は、ほとんどアンチパターンとして見られているので、Akka HTTPに対応するものはありません。 詳細はこちら:https://github.com/akka/akka/issues/18626

  • `` respondWithMediaType``はスプレーのアンチパターンとみなされ、Akka HTTPに移植されませんでした。 代わりに、ユーザーはAkka HTTPが実装するコンテントタイプネゴシエーションに頼るべきです。 詳細はこちら:https://github.com/akka/akka/issues/18625

  • :ref:`registeredCustomMediaTypes`がグローバルな状態に依存しないようにSprayから変更されています。

HttpServiceは取り除かれています

Sprayの``HttpService``は取り除かれています。これは以下のようなscalaのコード:

val service = system.actorOf(Props(new HttpServiceActor(routes)))
IO(Http)(system) ! Http.Bind(service, "0.0.0.0", port = 8080)

が以下のように変更する必要があることを意味しています:

Http().bindAndHandle(routes, "0.0.0.0", port = 8080)

マーシャリングの変更

Marshaller.ofは``Marshaller.withFixedContentType``で置き換えることができます。

置き換える前:

Marshaller.of[JsonApiObject](`application/json`) { (value, contentType, ctx) =>
  ctx.marshalTo(HttpEntity(contentType, value.toJson.toString))
}

置き換えた後:

Marshaller.withFixedContentType(`application/json`) { obj =>
  HttpEntity(`application/json`, obj.toJson.compactPrint)
}

Akka HTTPマーシャラはコンテントネゴシエーションをサポートしているので、他のマーシャラーから「スーパー」マーシャラーを作成するときにコンテンツタイプを指定する必要はありません。

前:

ToResponseMarshaller.oneOf(
  `application/vnd.api+json`,
  `application/json`
)(
  jsonApiMarshaller,
  jsonMarshaller
}

後:

Marshaller.oneOf(
  jsonApiMarshaller,
  jsonMarshaller
)

アンマーシャリングの変更

Akka Httpには、あらかじめ定義されたアンマーシャラーのセットが含まれています。 つまり、次のようなscalaのコードは:

Unmarshaller[Entity](`application/json`) {
  case HttpEntity.NonEmpty(contentType, data) =>
    data.asString.parseJson.convertTo[Entity]
}

が以下のように変更する必要があることを意味しています:

Unmarshaller
  .stringUnmarshaller
  .forContentTypes(`application/json`)
  .map(_.parseJson.convertTo[Entity])

MediaTypesの変更

``MediaType.custom``は``MediaType``オブジェクトの所定のメソッドで置き換えられます。

置き換える前:

MediaType.custom("application/vnd.acme+json")

置き換えた後:

MediaType.applicationWithFixedCharset("application/vnd.acme+json", HttpCharsets.`UTF-8`)

リジェクションハンドリングの変更

``RejectionHandler``はBuilderパターンとして使われています。以下の例を参照してください:

前:

def rootRejectionHandler = RejectionHandler {
  case Nil =>
    requestUri { uri =>
      logger.error("Route: {} does not exist.", uri)
      complete((NotFound, mapErrorToRootObject(notFoundError)))
    }
  case AuthenticationFailedRejection(cause, challengeHeaders) :: _ => {
    logger.error(s"Request is rejected with cause: $cause")
    complete((Unauthorized, mapErrorToRootObject(unauthenticatedError)))
  }
}

後:

RejectionHandler
.newBuilder()
.handle {
  case AuthenticationFailedRejection(cause, challengeHeaders) =>
    logger.error(s"Request is rejected with cause: $cause")
    complete((Unauthorized, mapErrorToRootObject(unauthenticatedError)))
.handleNotFound { ctx =>
  logger.error("Route: {} does not exist.", ctx.request.uri.toString())
  ctx.complete((NotFound, mapErrorToRootObject(notFoundError)))
}
.result()
.withFallback(RejectionHandler.default)

HTTPクライアントの変更

Sprayクライアントパイプラインは取り除かれました。Httpの``singleRequest`が``sendReceive``の代わりに使われるべきです:

//this will not longer work
val token = Authorization(OAuth2BearerToken(accessToken))
val pipeline: HttpRequest => Future[HttpResponse] = (addHeader(token) ~> sendReceive)
val patch: HttpRequest = Patch(uri, object))

pipeline(patch).map { response ⇒
    …
}

が以下のように変更する必要があることを意味しています:

val request = HttpRequest(
  method = PATCH,
  uri = Uri(uri),
  headers = List(Authorization(OAuth2BearerToken(accessToken))),
  entity = HttpEntity(MediaTypes.`application/json`, object)
)

http.singleRequest(request).map {
  case … => …
}

ヘッダーの変更

すべてのHTTPヘッダーは``akka.http.scaladsl.model.headers._``パッケージに移動しました。

フォームフィールドとファイルアップロードディレクティブの変更

HTTPエンティティのストリーミングの性質上、複数のフォームフィールドにアクセスする前に厳密なHTTPエンティティを持つか、ファイルアップロードディレクティブを使用することが重要です。 1つの解決策は、フォームフィールドを扱う前にnextディレクティブを使用することです:

val toStrict: Directive0 = extractRequest flatMap { request =>
  onComplete(request.entity.toStrict(5.seconds)) flatMap {
    case Success(strict) =>
      mapRequest( req => req.copy(entity = strict))
    case _ => reject
  }
}

あるいはこのようにも使うことができます:

toStrict {
  formFields("name".as[String]) { name =>
  ...
  }
}

Contents