如何在Mashaller中使用http请求头进行内容协商?

时间:2022-10-13 20:12:51

My app supports protobuf and JSON serialzation. For JSON serialization I use com.trueaccord.scalapb.json.JsonFormat, my dtos are generated from proto definitions.

我的app支持protobuf和JSON序列化。对于JSON序列化,我使用com.trueaccord.scalap . JSON。JsonFormat,我的dto是由proto定义生成的。

The com.trueaccord serializer wraps option types to JSON objects which is causing issues for some clients so I want to be able to support org.json4s without braking the existing clients.

com。trueaccord序列化器将选项类型封装到JSON对象中,这给一些客户端带来了问题,因此我希望能够支持org。json4s没有制动现有客户端。

I would like to be able to pick a serializer based on a custom http header called JFORMAT. The idea is that if this header is sent I will use json4s otherwise I will use the trueaccord serializer.

我希望能够根据称为JFORMAT的自定义http头选择序列化器。其思想是,如果发送了这个头,我将使用json4s,否则我将使用trueaccord序列化器。

I managed to create a Unmarshaller which can pick a request serializer based on a header value:

我创建了一个Unmarshaller,它可以根据头值选择请求序列化器:

Unmarshaller.withMaterializer[HttpRequest, T](_ => implicit mat => {
  case request: HttpRequest =>
    val entity = request.entity
    entity.dataBytes.runFold(ByteString.empty)(_ ++ _).map(data => {
      entity.contentType match {
        case `applicationJsonContentType` =>
          val jsFormat = {
            val header = request.headers.find(h => h.name() == jsonFormatHeaderName)
            if (header.isEmpty) "1.0" else header.get.value()
          }

          val charBuffer = Unmarshaller.bestUnmarshallingCharsetFor(entity)
          val jsonText = data.decodeString(charBuffer.nioCharset().name())
          val dto = if(jsFormat == "2.0") {
            write[T](value)(formats) // New Formatter
          } else {
            JsonFormat.fromJsonString[T](jsonText) // Old Formatter
          }
          dto
        case `protobufContentType` =>
          companion.parseFrom(CodedInputStream.newInstance(data.asByteBuffer)) // Proto Formatter
        case _ =>
          throw UnsupportedContentTypeException(applicationJsonContentType, protobufContentType)
      }
    })

I want to do the same with my Marshaller which I use with Marshaller.oneOf and the JSON handling one looks like:

我想对编组器做同样的事情我用编组器。其中之一和JSON处理的是:

  Marshaller.withFixedContentType(contentType) { value =>
    val jsonText = JsonSerializer.toJsonString[T](value)
    HttpEntity(contentType, jsonText)
  }

Is there a way to construct a Mashaller which is aware of the request http headers? The Akka HTTP docs don't have any examples and I cannot make sense of the PredefinedToRequestMarshallers.

是否有一种方法来构造一个了解请求http头的Mashaller ?Akka HTTP文档没有任何示例,我无法理解预定义的torequestmarshaller。

Do I need to combine multiple marshallers somehow or can I append some metadata to a context during the request serialization I can use later in the Marshaller? I want to avoid appending meta to my dto if possible or using a custom content type like application/vnd.api+json

我是否需要以某种方式组合多个封送器,或者我是否可以在稍后可以在封送器中使用的请求序列化期间将一些元数据附加到上下文?如果可能的话,我希望避免将meta追加到dto中,或者使用自定义内容类型,比如application/vnd.api+json

There are lots of other useful info I could use from the request when I format the response like Accept-Encoding, custom headers like unique request id to create a correlation id, I could add JSONP support by reading the callback query parmeter, etc.

当我格式化响应(如接受编码)、自定义标头(如惟一请求id)以创建相关id时,我可以从请求中使用许多其他有用的信息,我可以通过读取回调查询parmeter等添加JSONP支持。

To clarify: I need a solution to use the Mashaller, subclass of it or a custom version created by a factory method or maybe multiple Marshallers chained together. Marshaller.withFixedContentType already using the Accept header so there must be a way. I added added bounty to reward a solution to a specific challenge. I am ware of hacks and workarounds and I asked the question because I need a clean solution solving a specific scenario.

为了澄清:我需要一个解决方案来使用这个Mashaller,它的子类或者一个由工厂方法创建的自定义版本,或者可能多个编组链接在一起。信号员。withFixedContentType已经在使用Accept标头,因此必须有一种方法。我增加了赏金,以奖励针对某个特定挑战的解决方案。我很了解一些技巧和方法,我问这个问题是因为我需要一个干净的解决方案来解决一个特定的场景。

1 个解决方案

#1


0  

Custom Marshallers section mentions Marshaller.oneOf overloaded methods, that seems to be what you want:

自定义Marshallers部分提到了Marshaller。重载方法之一,这似乎就是你想要的:

Helper for creating a "super-marshaller" from a number of "sub-marshallers". Content-negotiation determines, which "sub-marshaller" eventually gets to do the job.

帮助从多个“子编组器”创建“超级编组器”。内容协商决定了“子编组器”最终要做的工作。

The Marshaller companion object has many methods that receive a Seq[HttpHeader]. You can look into their implementations as well.

Marshaller伙伴对象有许多接收Seq[HttpHeader]的方法。您也可以查看它们的实现。

I don't have the time to look into the source code myself, but if this is not enough to put you on the right path, let me know.

我没有时间亲自研究源代码,但如果这还不足以让您走上正确的道路,请让我知道。

Edit:

编辑:

How about?

怎么样?

get {
  optionalHeaderValueByName("JFORMAT") { format =>
    complete {
      format match {
        case Some(f) => "Complete with json4s"
        case _ => "Complete with trueaccord"
      }
    }
  }
}

#1


0  

Custom Marshallers section mentions Marshaller.oneOf overloaded methods, that seems to be what you want:

自定义Marshallers部分提到了Marshaller。重载方法之一,这似乎就是你想要的:

Helper for creating a "super-marshaller" from a number of "sub-marshallers". Content-negotiation determines, which "sub-marshaller" eventually gets to do the job.

帮助从多个“子编组器”创建“超级编组器”。内容协商决定了“子编组器”最终要做的工作。

The Marshaller companion object has many methods that receive a Seq[HttpHeader]. You can look into their implementations as well.

Marshaller伙伴对象有许多接收Seq[HttpHeader]的方法。您也可以查看它们的实现。

I don't have the time to look into the source code myself, but if this is not enough to put you on the right path, let me know.

我没有时间亲自研究源代码,但如果这还不足以让您走上正确的道路,请让我知道。

Edit:

编辑:

How about?

怎么样?

get {
  optionalHeaderValueByName("JFORMAT") { format =>
    complete {
      format match {
        case Some(f) => "Complete with json4s"
        case _ => "Complete with trueaccord"
      }
    }
  }
}