如何在 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 请求标头进行内容协商?的主要内容,如果未能解决你的问题,请参考以下文章