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

Posted

技术标签:

【中文标题】如何在 Mashaller 中使用 http 请求标头进行内容协商?【英文标题】:How can I use http request headers for content negotiation in a Mashaller? 【发布时间】:2017-12-07 10:02:09 【问题描述】:

我的应用支持 protobuf 和 JSON 序列化。对于 JSON 序列化,我使用 com.trueaccord.scalapb.json.JsonFormat,我的 dto 是从 proto 定义生成的。

com.trueaccord 序列化程序将选项类型包装到 JSON 对象中,这会导致某些客户端出现问题,因此我希望能够支持 org.json4s 而不会破坏现有客户端。

我希望能够选择基于名为 JFORMAT 的自定义 http 标头的序列化程序。这个想法是,如果发送此标头,我将使用 json4s,否则我将使用 trueaccord 序列化程序。

我设法创建了一个 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)
      
    )

我想对我与 Marshaller.oneOf 一起使用的 Marshaller 做同样的事情,JSON 处理看起来像:

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

有没有办法构造一个知道请求 http 标头的 Mashaller? Akka HTTP 文档没有任何示例,我无法理解 PredefinedToRequestMarshallers。

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

当我格式化响应(例如 Accept-Encoding)时,我可以从请求中使用许多其他有用的信息,自定义标头(例如唯一请求 ID 以创建关联 ID),我可以通过阅读 callback 查询来添加 JSONP 支持参数等。

澄清一下:我需要一个解决方案来使用 Mashaller、它的子类或由工厂方法创建的自定义版本,或者可能是多个链接在一起的 Marshaller。 Marshaller.withFixedContentType 已经在使用 Accept 标头,所以必须有办法。我添加了额外的赏金来奖励特定挑战的解决方案。我知道黑客和解决方法,我问了这个问题,因为我需要一个干净的解决方案来解决特定的场景。

【问题讨论】:

我可能在这里遗漏了一些东西:为什么不在收到请求后实例化你的编组器,并且一旦你知道你需要哪一个?对管理所有内容类型进行解组是有意义的,但是编组几乎就是您将答案转换为您想要的任何内容,因此如果您希望它依赖于请求,请将其作为请求的函数? 【参考方案1】:

Custom Marshallers 部分提到Marshaller.oneOf 重载方法,这似乎是您想要的:

帮助从多个 “子编组器”。内容协商决定,哪个 “sub-marshaller”最终会完成这项工作。

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

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

编辑

怎么样?

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

【讨论】:

我将 Marshaller.oneOf 与多个 Marshaller.withFixedContentType 编组器一起使用,但那些仅使用 Accept 标头。 WithOpenCharset 和 Opaque 也没有请求上下文的引用。 我需要一个可以与 oneOf 一起使用但允许基于自定义标题做出决策的 marsheller。 @JenoLaszlo 啊,您在问题中没有提到您已经在使用oneOf marshaller。您使用的是什么内容类型?而且,您是否考虑过使用Content-Type 标头来解决此问题,而不是回复自定义标头? 为新的 JSON 格式注册一个新的内容类型,如 application/vnd.api+json 工作正常,但它很脏。这实际上是我临时做的,直到我弄清楚如何获取请求标头。 看起来内容类型标头通常在解组器中使用,而接受标头在编组器中使用。如果未发送接受标头,则将使用 oneOf 中的第一个编组器,并且完全忽略内容类型。阅读请求标头的另一个原因:)

以上是关于如何在 Mashaller 中使用 http 请求标头进行内容协商?的主要内容,如果未能解决你的问题,请参考以下文章

Scala - 如何在 http 请求中不使用未来

我如何在异步中使用请求?

如何在 Ruby 中使用 HTTP 请求与 Spotify 合作

如何使用http在angular2中发出请求?

如何在 HTTP 请求中正确使用缓存控制标头

如何在 Flutter 中使用 JSON 正文发出 http DELETE 请求?