如何在 Akka HTTP 中将“text/plain”解组为 JSON

Posted

技术标签:

【中文标题】如何在 Akka HTTP 中将“text/plain”解组为 JSON【英文标题】:How to unmarshall `text/plain` as JSON in Akka HTTP 【发布时间】:2016-03-23 00:52:27 【问题描述】:

我正在使用一个旧的 HTTP API(我无法更改),它在正文中使用 JSON 进行响应,但会提供一个 Content-Type: text/plain; charset=utf-8 标头。

我正在尝试将 HTTP 正文解组为 JSON,但出现以下异常:akka.http.scaladsl.unmarshalling.Unmarshaller$UnsupportedContentTypeException: Unsupported Content-Type, supported: application/json

我的代码如下所示:

import spray.json.DefaultJsonProtocol
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import akka.http.scaladsl.unmarshalling._

case class ResponseBody(status: String, error_msg: String)

object ResponseBodyJsonProtocol extends DefaultJsonProtocol 
  implicit val responseBodyFormat = jsonFormat2(ResponseBody)


def parse(entity: HttpEntity): Future[ResponseBody] = 
  implicit val materializer: Materializer = ActorMaterializer()
  import ResponseBodyJsonProtocol._
  Unmarshal[HttpEntity](entity).to[ResponseBody]

HTTP 响应示例如下所示:

HTTP/1.1 200 OK
Cache-Control: private
Content-Encoding: gzip
Content-Length: 161
Content-Type: text/plain; charset=utf-8
Date: Wed, 16 Dec 2015 18:15:14 GMT
Server: Microsoft-IIS/7.5
Vary: Accept-Encoding
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET

"status":"1","error_msg":"Missing parameter"

如何忽略 HTTP 响应中的 Content-Type 并解析为 JSON?

【问题讨论】:

【参考方案1】:

我发现的一种解决方法是在解组之前手动设置Content-Type 上的HttpEntity

def parse(entity: HttpEntity): Future[ResponseBody] = 
  implicit val materializer: Materializer = ActorMaterializer()
  import ResponseBodyJsonProtocol._
  Unmarshal[HttpEntity](entity.withContentType(ContentTypes.`application/json`)).to[ResponseBody]

似乎工作正常,但我对其他想法持开放态度......

【讨论】:

我遇到了类似的问题,并使用了您的最后一行代码进行解组。出于某种原因, withContentType 改变了实体并将其转换为一个严格的实体,该实体也包含内容类型对象。关于如何从中提取身体的任何想法?【参考方案2】:

我会使用map... 指令。它看起来又短又优雅。

val routes = (decodeRequest & encodeResponse) 
  mapResponseEntity(_.withContentType(ContentTypes.`application/json`)) 
    nakedRoutes ~ authenticatedRoutes
  

【讨论】:

我不确定这是否适用......在我的情况下没有路由,我当然看不出这比我的答案更短或更优雅。不过同样的想法。 哦,是的,如果您不使用 Spray DSL,则它不适用。【参考方案3】:

另一种解决方案:创建一个接受任何内容类型(或任何内容类型列表)的自定义解组器:

import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport.sprayJsValueByteStringUnmarshaller
import akka.http.scaladsl.model.ContentTypeRange
import akka.http.scaladsl.unmarshalling.FromEntityUnmarshaller, Unmarshaller
import spray.json.RootJsonReader, jsonReader

// Add this to scope, INSTEAD OF importing SprayJsonSupport._
// It returns an Unmarshaller identical to SprayJsonSupport.sprayJsonUnmarshaller, but with a custom validation on content type
implicit def lenientJsonUnmarshaller[T](implicit reader: RootJsonReader[T]): FromEntityUnmarshaller[T] =
  Unmarshaller.byteStringUnmarshaller
    .forContentTypes(ContentTypeRange.*) // or any range you'd like
    .andThen(sprayJsValueByteStringUnmarshaller)
    .map(jsonReader[T].read)

然后 - 在范围内,继续你做的:

def parse(entity: HttpEntity): Future[ResponseBody] = 
  implicit val materializer: Materializer = ActorMaterializer()
  import ResponseBodyJsonProtocol._
  Unmarshal[HttpEntity](entity).to[ResponseBody] // this implicitly uses the custom unmarshaller

好处 - 很容易重用这个隐式解组器 - 只需编写一次,然后在需要的地方导入它,而不必在实体上设置内容类型。

【讨论】:

以上是关于如何在 Akka HTTP 中将“text/plain”解组为 JSON的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Akka Http 中使用 HttpResponse

akka http:Akka 流与演员建立休息服务

Akka HTTP:如何将 Json 格式响应解组为域对象

如何使用Scala / Akka Http处理多个HTTP头

如何记录 Akka HTTP 客户端请求

如何使用 AKKA-HTTP、spray-json、oauth2 和 slick 优化 scala REST api?