Marshalling/编组

译注:编码、组合

TODO overhaul for Java

Marshalling is the process of converting a higher-level (object) structure into some kind of lower-level representation, often a “wire format”. Other popular names for marshalling are “serialization” or “pickling”.

编组是指转换一个高阶(对象)结构到某种低阶表示的过程。其它流行的叫法是“序列化”(serialization)或“腌制”(pickling)。

In Akka HTTP, marshalling means the conversion of an object of type T into a lower-level target type, e.g. a MessageEntity (which forms the “entity body” of an HTTP request or response) or a full HttpRequestHttpRequest or HttpResponseHttpResponse.

在 Akka HTTP 里面,编组意味着转换一个 T 类型的对像到一个低级的目标类型,例如:MessageEntity(构成一个 HTTP 请求或响应的“实体正文”) 或者 HttpRequestHttpRequest 或者 apidoc[HttpResponse]。

On the server-side, for example, marshalling is used to convert an application-domain object to a response entity. Requests can contain an AcceptAccept header that lists acceptable content types for the client, such as application/json and application/xml. A marshaller contains the logic to negotiate the result content types based on the AcceptAccept and the AcceptCharset headers.

在服务器端,编组被用于转换应用域对象到一个响应实体。请求可以包含一个 AcceptAccept 头列出客户端可接收到内容类型,例如 application/jsonapplication/xml。一个编组器包含根据 AcceptAcceptAcceptCharset 头协商结果内容类型的逻辑。

Basic Design

基础设计

Marshalling of instances of type A into instances of type B is performed by a Marshaller<A, B>Marshaller[A, B].

编码类型 A 实例到类型 B 实例由 Marshaller<A, B>Marshaller[A, B] 执行。

Contrary to what you might initially expect, Marshaller<A, B>Marshaller[A, B] is not a plain function A => B but rather essentially a function A => Future[List[Marshalling[B]]]A => CompletionStage<List<Marshalling<B>>>. Let’s dissect this rather complicated looking signature piece by piece to understand why marshallers are designed this way. Given an instance of type A a Marshaller<A, B>Marshaller[A, B] produces:

与你最初的期望相反,@apidoc[Marshaller[A, B]] 不是一个普通的 A => B 函数,本质上是一个 A => Future[List[Marshalling[B]]]A => CompletionStage<List<Marshalling<B>>> 函数。 让我们逐步剖析这个看起来相当复杂的签名,以理解设计人员为什么要这么设计。 给定一个类型 A 的实例,@apidoc[Marshaller[A, B]] 生成:

  1. A FutureCompletionStage: This is probably quite clear. Marshallers are not required to synchronously produce a result, so instead they return a future, which allows for asynchronicity in the marshalling process. FutureCompletionStage:这个可能相对比较清晰。编组器不一定要同步产生一个结果,所以返回一个 Future,以便编组过程可以异步完成。

  2. of List: Rather than only a single target representation for A marshallers can offer several ones. Which one will be rendered onto the wire in the end is decided by content negotiation. For example, the Marshaller<OrderConfirmation, MessageEntity>Marshaller[OrderConfirmation, MessageEntity] might offer a JSON as well as an XML representation. The client can decide through the addition of an AcceptAccept request header which one is preferred. If the client doesn’t express a preference the first representation is picked. 有 List:与其返回一个单一的表达类型,类型 A 的编组器可以提供几个不同的目标类型。具体哪个类型作为最终渲染用到通讯渠道上,则依赖于双方的内容协商。

  3. of Marshalling[B]Marshalling<B>: Rather than returning an instance of B directly marshallers first produce a Marshalling[B]Marshalling<B>. This allows for querying the MediaTypeMediaType and potentially the HttpCharsetHttpCharset that the marshaller will produce before the actual marshalling is triggered. Apart from enabling content negotiation this design allows for delaying the actual construction of the marshalling target instance to the very last moment when it is really needed. 有 Marshalling[B]Marshalling<B>:与其直接返回一个 B 目标类型的实例,编组器会先返回一个 Marshalling[B] 类型。这使编组器可以在进入编组过程前先查询确认 MediaTypeHttpCharset。这样的设计既可以支持内容协商,又可以使具体的目标编组对象推迟到有需要的时候才被构建。

This is how Marshalling is defined:

这是 Marshalling 的定义方式:

/**
 * Describes one possible option for marshalling a given value.
 */
sealed trait Marshalling[+A] {
  def map[B](f: A => B): Marshalling[B]

  /**
   * Converts this marshalling to an opaque marshalling, i.e. a marshalling result that
   * does not take part in content type negotiation. The given charset is used if this
   * instance is a `WithOpenCharset` marshalling.
   */
  def toOpaque(charset: HttpCharset): Marshalling[A]
}

object Marshalling {

  /**
   * A Marshalling to a specific [[akka.http.scaladsl.model.ContentType]].
   */
  final case class WithFixedContentType[A](
    contentType: ContentType,
    marshal:     () => A) extends Marshalling[A] {
    def map[B](f: A => B): WithFixedContentType[B] = copy(marshal = () => f(marshal()))
    def toOpaque(charset: HttpCharset): Marshalling[A] = Opaque(marshal)
  }

  /**
   * A Marshalling to a specific [[akka.http.scaladsl.model.MediaType]] with a flexible charset.
   */
  final case class WithOpenCharset[A](
    mediaType: MediaType.WithOpenCharset,
    marshal:   HttpCharset => A) extends Marshalling[A] {
    def map[B](f: A => B): WithOpenCharset[B] = copy(marshal = cs => f(marshal(cs)))
    def toOpaque(charset: HttpCharset): Marshalling[A] = Opaque(() => marshal(charset))
  }

  /**
   * A Marshalling to an unknown MediaType and charset.
   * Circumvents content negotiation.
   */
  final case class Opaque[A](marshal: () => A) extends Marshalling[A] {
    def map[B](f: A => B): Opaque[B] = copy(marshal = () => f(marshal()))
    def toOpaque(charset: HttpCharset): Marshalling[A] = this
  }
}

Akka HTTP also defines a number of helpful aliases for the types of marshallers that you’ll likely work with most:

Akka HTTP 为你可能用到的大多数编组也定义了一些有用的别名:

type ToEntityMarshaller[T] = Marshaller[T, MessageEntity]
type ToByteStringMarshaller[T] = Marshaller[T, ByteString]
type ToHeadersAndEntityMarshaller[T] = Marshaller[T, (immutable.Seq[HttpHeader], MessageEntity)]
type ToResponseMarshaller[T] = Marshaller[T, HttpResponse]
type ToRequestMarshaller[T] = Marshaller[T, HttpRequest]

Predefined Marshallers

预定义编组器

Akka HTTP already predefines a number of marshallers for the most common types. Specifically these are:

Akka HTTP 对多数常见类型预定了一些编组器。具体来说,这些是:

All marshallers can be found in MarshallerMarshaller.

所有编组器中在 MarshallerMarshaller 找到。

Implicit Resolution

隐式处理

The marshalling infrastructure of Akka HTTP relies on a type-class based approach, which means that MarshallerMarshaller instances from a certain type A to a certain type B have to be available implicitly.

Akka HTTP 编组的基础设施实现基于(Scala)类型类方法,这意味着从 A 类型到 B 类型的 MarshallerMarshaller 实例必需作为一个隐式参数/函数存在。

The implicits for most of the predefined marshallers in Akka HTTP are provided through the companion object of the MarshallerMarshaller trait. This means that they are always available and never need to be explicitly imported. Additionally, you can simply “override” them by bringing your own custom version into local scope.

Akka HTTP 中大部分预定义的编组器的隐式工具都是通过 MarshallerMarshaller trait 的伴身对象提供的。这意味着它们总是可用,而不需要显示导入。 另外,你可以在局部可视范围里用自己的版本覆盖原有版本。

Custom Marshallers

自定义编组器

Akka HTTP gives you a few convenience tools for constructing marshallers for your own types. Before you do that you need to think about what kind of marshaller you want to create. If all your marshaller needs to produce is a MessageEntity then you should probably provide a ToEntityMarshaller[T]Marshaller<T, MessageEntity>Marshaller[T, MessageEntity]. The advantage here is that it will work on both the client- as well as the server-side since a ToReponseMarshaller[T]Marshaller<T, HttpResponse>Marshaller[T, HttpResponse] as well as a ToRequestMarshaller[T]Marshaller<T, HttpRequest>Marshaller[T, HttpRequest] can automatically be created if a ToEntityMarshaller[T]Marshaller<T, MessageEntity>Marshaller[T, MessageEntity] is available.

Akka HTTP 为构造你自己类型的编组器提供了一些便利工具。在执行此操作之前,你需要考虑想要创建哪种类型的编组器。 如果你的编组器需要生成一个 MessageEntity,则你可能应该提供一个 ToEntityMarshaller[T]Marshaller<T, MessageEntity>Marshaller[T, MessageEntity]。这个的优势是它可工作于客户-以及服务器-端两种场合,因为,如果 ToEntityMarshaller[T]Marshaller<T, MessageEntity>Marshaller[T, MessageEntity] 可用, ToReponseMarshaller[T]Marshaller<T, HttpResponse>Marshaller[T, HttpResponse] 以及 ToRequestMarshaller[T]Marshaller<T, HttpRequest>Marshaller[T, HttpRequest] 能自动被生成。

If, however, your marshaller also needs to set things like the response status code, the request method, the request URI or any headers then a ToEntityMarshaller[T]Marshaller<T, MessageEntity>Marshaller[T, MessageEntity] won’t work. You’ll need to fall down to providing a ToResponseMarshaller[T]Marshaller<T, HttpResponse>Marshaller[T, HttpResponse] or a ToRequestMarshaller[T]]Marshaller<T, HttpRequest>Marshaller[T, HttpRequest] directly.

但是,如果如果你的组织器需要设置其它内容,像响应状态、请求方法、请求 URI 或任何头域,那 ToEntityMarshaller[T]Marshaller<T, MessageEntity>Marshaller[T, MessageEntity] 不能工作。你需要直接提供一个 ToResponseMarshaller[T]Marshaller<T, HttpResponse>Marshaller[T, HttpResponse]ToRequestMarshaller[T]]Marshaller<T, HttpRequest>Marshaller[T, HttpRequest]

For writing your own marshallers you won’t have to “manually” implement the MarshallerMarshaller traitclass directly.

对于编写自己的编组器,不需要直接“手动”实现 MarshallerMarshaller trait

Rather, it should be possible to use one of the convenience construction helpers defined on the MarshallerMarshaller companion:

相反,应该使用在 MarshallerMarshaller 伴身对象中定义的便利构造助手。

object Marshaller
  extends GenericMarshallers
  with PredefinedToEntityMarshallers
  with PredefinedToResponseMarshallers
  with PredefinedToRequestMarshallers {

  /**
   * Creates a [[Marshaller]] from the given function.
   */
  def apply[A, B](f: ExecutionContext => A => Future[List[Marshalling[B]]]): Marshaller[A, B] =
    new Marshaller[A, B] {
      def apply(value: A)(implicit ec: ExecutionContext) =
        try f(ec)(value)
        catch { case NonFatal(e) => FastFuture.failed(e) }
    }

  /**
   * Helper for creating a [[Marshaller]] using the given function.
   */
  def strict[A, B](f: A => Marshalling[B]): Marshaller[A, B] =
    Marshaller { _ => a => FastFuture.successful(f(a) :: Nil) }

  /**
   * Helper for creating a "super-marshaller" from a number of "sub-marshallers".
   * Content-negotiation determines, which "sub-marshaller" eventually gets to do the job.
   *
   * Please note that all marshallers will actually be invoked in order to get the Marshalling object
   * out of them, and later decide which of the marshallings should be returned. This is by-design,
   * however in ticket as discussed in ticket https://github.com/akka/akka-http/issues/243 it MAY be
   * changed in later versions of Akka HTTP.
   */
  def oneOf[A, B](marshallers: Marshaller[A, B]*): Marshaller[A, B] =
    Marshaller { implicit ec => a => FastFuture.sequence(marshallers.map(_(a))).fast.map(_.flatten.toList) }

  /**
   * Helper for creating a "super-marshaller" from a number of values and a function producing "sub-marshallers"
   * from these values. Content-negotiation determines, which "sub-marshaller" eventually gets to do the job.
   *
   * Please note that all marshallers will actually be invoked in order to get the Marshalling object
   * out of them, and later decide which of the marshallings should be returned. This is by-design,
   * however in ticket as discussed in ticket https://github.com/akka/akka-http/issues/243 it MAY be
   * changed in later versions of Akka HTTP.
   */
  def oneOf[T, A, B](values: T*)(f: T => Marshaller[A, B]): Marshaller[A, B] =
    oneOf(values map f: _*)

  /**
   * Helper for creating a synchronous [[Marshaller]] to content with a fixed charset from the given function.
   */
  def withFixedContentType[A, B](contentType: ContentType)(marshal: A => B): Marshaller[A, B] =
    new Marshaller[A, B] {
      def apply(value: A)(implicit ec: ExecutionContext) =
        try FastFuture.successful {
          Marshalling.WithFixedContentType(contentType, () => marshal(value)) :: Nil
        } catch {
          case NonFatal(e) => FastFuture.failed(e)
        }

      override def compose[C](f: C => A): Marshaller[C, B] =
        Marshaller.withFixedContentType(contentType)(marshal compose f)
    }

  /**
   * Helper for creating a synchronous [[Marshaller]] to content with a negotiable charset from the given function.
   */
  def withOpenCharset[A, B](mediaType: MediaType.WithOpenCharset)(marshal: (A, HttpCharset) => B): Marshaller[A, B] =
    new Marshaller[A, B] {
      def apply(value: A)(implicit ec: ExecutionContext) =
        try FastFuture.successful {
          Marshalling.WithOpenCharset(mediaType, charset => marshal(value, charset)) :: Nil
        } catch {
          case NonFatal(e) => FastFuture.failed(e)
        }

      override def compose[C](f: C => A): Marshaller[C, B] =
        Marshaller.withOpenCharset(mediaType)((c: C, hc: HttpCharset) => marshal(f(c), hc))
    }

  /**
   * Helper for creating a synchronous [[Marshaller]] to non-negotiable content from the given function.
   */
  def opaque[A, B](marshal: A => B): Marshaller[A, B] =
    strict { value => Marshalling.Opaque(() => marshal(value)) }

  /**
   * Helper for creating a [[Marshaller]] combined of the provided `marshal` function
   * and an implicit Marshaller which is able to produce the required final type.
   */
  def combined[A, B, C](marshal: A => B)(implicit m2: Marshaller[B, C]): Marshaller[A, C] =
    Marshaller[A, C] { ec => a => m2.compose(marshal).apply(a)(ec) }
}

Deriving Marshallers

衍生编组器

Sometimes you can save yourself some work by reusing existing marshallers for your custom ones. The idea is to “wrap” an existing marshaller with some logic to “re-target” it to your type.

有时,可通过复用已存在的编组器为自定义编组器节省一些工作。思路上就是以某种逻辑“包装”已存在的编组器以便“调整下目标”来对应你自己的类型。

In this regard wrapping a marshaller can mean one or both of the following two things:

在这方面,包装一个编组器可能意味着下面两件事之一或全部。

  • Transform the input before it reaches the wrapped marshaller
  • Transform the output of the wrapped marshaller
  • 在输入到达包装的编组器之前进行转换

  • 已包装的编组器的输出进行转换

For the latter (transforming the output) you can use baseMarshaller.map, which works exactly as it does for functions. For the former (transforming the input) you have four alternatives:

对于后者(输出进行轮换)你可以使用 baseMarshaller.map,这与使用原生的函数(函数的map)一样。对于前者(输入进行转换)你有四种选择:

  • baseMarshaller.compose
  • baseMarshaller.composeWithEC
  • baseMarshaller.wrap
  • baseMarshaller.wrapWithEC

compose works just like it does for functions. wrap is a compose that allows you to also change the ContentType that the marshaller marshals to. The ...WithEC variants allow you to receive an ExecutionContext internally if you need one, without having to depend on one being available implicitly at the usage site.

compose 与使用原生的函数(函数的compose)一样,wrap 是一个组合函数,允许你改变原编组器使用的 ContentType...WithEC 这些允许你如果有需要可以使用内部挂上的 ExecutionContext ,而无需另外在当前作用域找一个。

Using Marshallers

使用编组器

In many places throughout Akka HTTP, marshallers are used implicitly, e.g. when you define how to complete a request using the Routing DSL.

Akka HTTP 的很多地方,编组器被隐式使用,例如:当你用 路由 DSL complete 一个请求时。

However, you can also use the marshalling infrastructure directly if you wish, which can be useful for example in tests. The best entry point for this is the Marshal object, which you can use like this:

但是,如果你希望,也可以直接使用编组器基础设施,例如在测试中可能很有用。 Marshal object 是最好的切入点,你可以这样使用:

import scala.concurrent.Await
import scala.concurrent.duration._
import akka.http.scaladsl.marshalling.Marshal
import akka.http.scaladsl.model._

import system.dispatcher // ExecutionContext

val string = "Yeah"
val entityFuture = Marshal(string).to[MessageEntity]
val entity = Await.result(entityFuture, 1.second) // don't block in non-test code!
entity.contentType shouldEqual ContentTypes.`text/plain(UTF-8)`

val errorMsg = "Easy, pal!"
val responseFuture = Marshal(420 -> errorMsg).to[HttpResponse]
val response = Await.result(responseFuture, 1.second) // don't block in non-test code!
response.status shouldEqual StatusCodes.EnhanceYourCalm
response.entity.contentType shouldEqual ContentTypes.`text/plain(UTF-8)`

val request = HttpRequest(headers = List(headers.Accept(MediaTypes.`application/json`)))
val responseText = "Plaintext"
val respFuture = Marshal(responseText).toResponseFor(request) // with content negotiation!
a[Marshal.UnacceptableResponseContentTypeException] should be thrownBy {
  Await.result(respFuture, 1.second) // client requested JSON, we only have text/plain!
}

However, many directives dealing with marshalling also require that you pass a marshaller explicitly. The following example shows how to marshal Java bean classes to JSON using the Jackson JSON support:

但是,很多有关 marshalling 的指令也需求你显示传一个编组器。 下面的示例显示怎样使用 Jackson JSON support 编码 Java bean 类到 JSON:

import akka.http.javadsl.marshallers.jackson.Jackson;
import akka.http.javadsl.model.StatusCodes;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import static akka.http.javadsl.server.Directives.*;
import static akka.http.javadsl.unmarshalling.StringUnmarshallers.INTEGER;

private static Route putPetHandler(Map<Integer, Pet> pets, Pet thePet) {
    pets.put(thePet.getId(), thePet);
    return complete(StatusCodes.OK, thePet, Jackson.<Pet>marshaller());
}

private static Route alternativeFuturePutPetHandler(Map<Integer, Pet> pets, Pet thePet) {
    pets.put(thePet.getId(), thePet);
  CompletableFuture<Pet> futurePet = CompletableFuture.supplyAsync(() -> thePet);
    return completeOKWithFuture(futurePet, Jackson.<Pet>marshaller());
}
在此文档中发现错误?该页面的源代码可以在 这里 找到。欢迎随时编辑并提交 Pull Request。