Akka-http-json“不支持的内容类型,支持:application / json”

时间:2017-03-04 20:03:57

标签: json scala akka-http

我在使用自定义JSON marshaller / unmarshaller时遇到问题。这很好用:

trait EWorksJsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
  implicit object IndividualJsonFormat extends RootJsonFormat[Individual] {
    def write(individual: Individual) = JsObject(
      // blah blah blah
    )

    def read(value: JsValue): Individual = {
      // blah blah blah
    }
}

问题是返回Unsupported Content-Type, supported: application/json,如下所示:

import akka.http.scaladsl.model.ContentTypes._
import akka.http.scaladsl.model.HttpEntity
import akka.http.scaladsl.testkit.ScalatestRouteTest
import akka.http.scaladsl.unmarshalling._
import eworks.model.immutableModel.SpeciesAll
import eworks.model.mutableModel.{Individual, Individuals, VirtualWorld}
import eworks.model.{Fixtures, LoadableModel, SpeciesDefaultLike}
import org.junit.runner.RunWith
import org.scalatest.Matchers._
import org.scalatest._
import org.scalatest.junit.JUnitRunner
import spray.json._

@RunWith(classOf[JUnitRunner])
class TestRest extends WordSpec with SpeciesDefaultLike with LoadableModel with ScalatestRouteTest with Fixtures with EWorksJsonSupport {    
  "EWorksJsonSupport" should {
    "work for Individuals" in {
      val jsObject: JsValue = harry.toJson
      val entity = HttpEntity(`application/json`, jsObject.toString)

      Post("/addIndividual", entity) ~> new RestHttp()(speciesDefaults).route ~> check {
        handled === true
        contentType === `application/json`
        status.intValue === 200

        val individual1 = Unmarshal(response.entity).to[Individual] 
        // ErrorFuture(akka.http.scaladsl.unmarshalling.Unmarshaller$UnsupportedContentTypeException: Unsupported Content-Type, supported: application/json)
        val individual2 = responseAs[Individual]
        responseAs[Individual] shouldBe harry
      }
    }
  }
}

3 个答案:

答案 0 :(得分:1)

您通过将您的实体发布到HttpResponse(如已记录,见下文)从new RestHttp()(speciesDefaults).route路由器获得的/addIndividual响应已将text/plain作为内容类型,您应该修复它。它的内容看起来也不像有效的JSON(见下文)。

回应是:

HttpResponse(
    200 OK,
    List(),
    HttpEntity.Strict(
        text/plain; charset=UTF-8, 
        Individual added: harry is a human; (unborn); lifeStage 'adult'
    ), HttpProtocol(HTTP/1.1)
)

答案 1 :(得分:1)

如果您不能更改内容类型,则可以:

    val stringR : String = Await.result(Unmarshal(r).to[String],Duration.Inf)
    val ind : Individual = Unmarshal(stringR).to[Individual]

答案 2 :(得分:0)

解决方案的关键是使用所需的complete来呼叫ContentType。这是我编写的一种方法,它提供HttpResponse Content-Type application/json以及在评估block时计算的所需内容:

@inline def wrap(block: => JsValue): StandardRoute =
  complete(
    try {
      HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, success(block)))
    } catch {
      case e: Exception =>
        HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, error(e.getMessage)))
    }
  )

我做了一个特性来封装这个方便的实用工具方法:

import akka.http.scaladsl.model.{ContentTypes, HttpEntity, HttpHeader, HttpResponse}
import akka.http.scaladsl.server.{Directives, MediaTypeNegotiator, Route, StandardRoute, UnsupportedRequestContentTypeRejection}
import akka.http.scaladsl.unmarshalling._
import spray.json._
import scala.collection.immutable.Seq

trait RestHttpSupport extends Directives {
  @inline def error  (msg: String): String = JsObject("error"   -> JsString(msg)).prettyPrint
  @inline def success(msg: String): String = JsObject("success" -> JsString(msg)).prettyPrint

  @inline def error  (msg: JsValue): String = JsObject("error"   -> msg).prettyPrint
  @inline def success(msg: JsValue): String = JsObject("success" -> msg).prettyPrint

  @inline def wrap(block: => JsValue): StandardRoute =
    complete(
      try {
        HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, success(block)))
      } catch {
        case e: Exception =>
          HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, error(e.getMessage)))
      }
    )

  @inline def completeAsJson[T](requestHeaders: Seq[HttpHeader])
                               (body: T => StandardRoute)
                               (implicit um: FromRequestUnmarshaller[T]): Route = {
    import akka.http.scaladsl.model.MediaTypes.`application/json`
    if (new MediaTypeNegotiator(requestHeaders).isAccepted(`application/json`)) {
      entity(as[T]) { body }
    } else {
      reject(UnsupportedRequestContentTypeRejection(Set(`application/json`)))
    }
  }

  @inline def postAsJson[T](body: T => StandardRoute)
                   (implicit um: FromRequestUnmarshaller[T]): Route = {
    (post & extract(_.request.headers)) { requestHeaders =>
      completeAsJson[T](requestHeaders) { body }
    }
  }
}

混合了一个特征,假设从SprayJsonSupport with DefaultJsonProtocol构建的隐式序列化程序在范围内,可以使用wrap方法定义Akka HTTP路径。所有这些代码都来自EmpathyWorks™(非开源):

path("definedEvents") {
  get { wrap(allDefinedEvents.toJson) }
} ~
path("listIndividuals") {
  get { wrap(individuals.toJson) }
} ~
path("listSpecies") {
  get { wrap(speciesAll.toJson) }
} ~
path("listSpeciesNames") {
  get { wrap(speciesAll.collection.map(_.name).toJson) }
}