【问题标题】:akka HttpResponse read body as String scalaakka HttpResponse 将正文读取为 String scala
【发布时间】:2015-11-25 18:08:52
【问题描述】:

所以我有一个带有这个签名的函数(akka.http.model.HttpResponse):

def apply(query: Seq[(String, String)], accept: String): HttpResponse

我只是在如下测试中得到一个值:

val resp = TagAPI(Seq.empty[(String, String)], api.acceptHeader)

我想在类似的测试中检查它的主体:

resp.entity.asString == "tags"

我的问题是如何将响应正文作为字符串获取?

【问题讨论】:

  • 你在使用akka-http-testkit吗?如果是的话,你可以在测试中使用entityAs[String]来获取body作为String值。
  • 我需要使用 PlaySpec 所以我不能使用 akka-http-testkit :(
  • 我注意到如果将 akka-http-circe 的 FailFastCirceSupport (1.22.0) 带入上下文,entityAs[String] 将不起作用。解决方法是移动导入。

标签: scala akka akka-http


【解决方案1】:
import akka.http.scaladsl.unmarshalling.Unmarshal

implicit val system = ActorSystem("System")  
implicit val materializer = ActorFlowMaterializer() 

val responseAsString: Future[String] = Unmarshal(entity).to[String]

【讨论】:

  • 您使用的是哪个 akka-http-core 版本?对于 2.4.6,我在任何地方都找不到 akka.http.scaladsl.unmarshalling
  • @akauppi 这是akka-http-experimental 不是akka-http-core
  • 不幸的是 Unmarshal 生活在 akka-http 工件中(不是 akka-http-coreakka-http-client)。
  • 认为应该是 ActorMaterializer() 而不是 ActorFlowMaterializer()?
【解决方案2】:

不幸的是,在我的情况下,Unmarshal 到 String 无法正常工作,抱怨:Unsupported Content-Type, supported: application/json。那将是更优雅的解决方案,但我不得不使用另一种方式。在我的测试中,我使用从响应实体中提取的 Future 和 Await(来自 scala.concurrent)从 Future 获取结果:

Put("/post/item", requestEntity) ~> route ~> check {
  val responseContent: Future[Option[String]] =
  response.entity.dataBytes.map(_.utf8String).runWith(Sink.lastOption)

  val content: Option[String] = Await.result(responseContent, 10.seconds)
  content.get should be(errorMessage)
  response.status should be(StatusCodes.InternalServerError)
}

如果您需要遍历响应中的所有行,可以使用runForeach of Source:

 response.entity.dataBytes.map(_.utf8String).runForeach(data => println(data))

【讨论】:

    【解决方案3】:

    这是从请求正文中提取string 的简单指令

    def withString(): Directive1[String] = {
      extractStrictEntity(3.seconds).flatMap { entity =>
        provide(entity.data.utf8String)
      }
    }
    

    【讨论】:

      【解决方案4】:
      Unmarshaller.stringUnmarshaller(someHttpEntity)
      

      像魅力一样工作,也需要隐式实现器

      【讨论】:

        【解决方案5】:

        这是我的工作示例,

          import akka.actor.ActorSystem
          import akka.http.scaladsl.Http
          import akka.http.scaladsl.model._
          import akka.stream.ActorMaterializer
          import akka.util.ByteString
        
          import scala.concurrent.Future
          import scala.util.{ Failure, Success }
        
          def getDataAkkaHTTP:Unit = {
        
            implicit val system = ActorSystem()
            implicit val materializer = ActorMaterializer()
            // needed for the future flatMap/onComplete in the end
            implicit val executionContext = system.dispatcher
        
            val url = "http://localhost:8080/"
            val responseFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(uri = url))
        
            responseFuture.onComplete {
              case Success(res) => {
                val HttpResponse(statusCodes, headers, entity, _) = res
                println(entity)
                entity.dataBytes.runFold(ByteString(""))(_ ++ _).foreach (body => println(body.utf8String))
                system.terminate()
              }
              case Failure(_) => sys.error("something wrong")
            }
        
        
          }
        
        【解决方案6】:

        你也可以试试这个。

        responseObject.entity.dataBytes.runFold(ByteString(""))(_ ++ _).map(_.utf8String) map println
        

        【讨论】:

          【解决方案7】:

          由于 Akka Http 是基于流的,因此实体也是流的。如果你真的需要一次完整的字符串,你可以将传入的请求转换成Strict一个:

          这是通过使用toStrict(timeout: FiniteDuration)(mat: Materializer) API 在给定的时间限制内将请求收集到一个严格的实体中完成的(这很重要,因为您不想“尝试永远收集实体”以防传入请求实际上永远不会结束):

          import akka.stream.ActorFlowMaterializer
          import akka.actor.ActorSystem
          
          implicit val system = ActorSystem("Sys") // your actor system, only 1 per app
          implicit val materializer = ActorFlowMaterializer() // you must provide a materializer
          
          import system.dispatcher
          import scala.concurrent.duration._
          val timeout = 300.millis
          
          val bs: Future[ByteString] = entity.toStrict(timeout).map { _.data }
          val s: Future[String] = bs.map(_.utf8String) // if you indeed need a `String`
          

          【讨论】:

          • 这似乎是一个好方法,但你使用的是哪种隐式 FlowMaterializer? (你是从导入中得到它还是使用一些默认实现?)
          • 您可以只创建一个临时实例或重新使用现有实例,我修改了我的回复。
          • 我认为解组器是执行此操作的最新方法,因此我重新分配了答案标记。
          • 我想两者都可以。干杯
          • 哇,为什么是超时和必需的参数?让它使用起来更复杂和麻烦的方法..明智的默认会好得多。
          猜你喜欢
          • 2017-04-15
          • 1970-01-01
          • 2021-02-02
          • 1970-01-01
          • 2020-05-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多