Akka HttpResponse如何将body读取为String(使用Scala)

38

所以我有一个函数,其参数是这样的(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"
我的问题是如何将响应主体作为字符串获取?

相关:https://dev59.com/b43da4cB1Zd3GeqPwzmT - Brian
3
你是否正在使用akka-http-testkit?如果是,你可以在测试中使用entityAs[String]来获取响应体的字符串值。 - jrudolph
我需要使用PlaySpec,所以我不能使用akka-http-testkit :( - tg44
我注意到如果引入了akka-http-circe的FailFastCirceSupport(1.22.0),entityAs[String]将无法工作。解决方法是移动导入语句。 - akauppi
7个回答

43
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
1
@akauppi 这是 akka-http-experimental,不是 akka-http-core - wlk
1
不幸的是,Unmarshal 存在于 akka-http 构件中(而不是 akka-http-coreakka-http-client)。 - Tvaroh
1
你认为应该使用ActorMaterializer()而不是ActorFlowMaterializer()吗? - user79074

30

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?(你是从import导入还是使用了一些默认实现?) - tg44
您可以创建一个临时实例或重复使用现有实例,我修改了我的回复。 - Konrad 'ktoso' Malawski
我认为Unmarshaller是最新的方法,因此我重新分配了答案勾选。 - tg44
任意一个都可以,我想。干杯。 - Konrad 'ktoso' Malawski
哇,为什么超时是必需的参数?这样做只会让使用变得更加复杂和繁琐。设置一个合理的默认值会更好。 - Vistritium

12

你也可以尝试这一个。

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

5

这里有一个简单的指令,从请求体中提取字符串

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

5
Unmarshaller.stringUnmarshaller(someHttpEntity)

运行得非常好,还需要隐式材料化器。


4

很不幸,在我的情况下,将Unmarshal转换为字符串没有正常工作,并抱怨:不支持的内容类型,支持: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)
}

如果您需要逐行查看响应中的所有内容,可以使用 Source 的 runForeach 方法:

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

尽管我正在扩展FailFastCirceSupport,但这个解决方案对我有效。一些依赖于Unmarshal(…).to[String]的其他答案没有起作用,可能是因为我正在扩展该特性。 - Daniel Werner

1

这是我的工作示例,

  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")
    }


  }

参考:https://doc.akka.io/docs/akka-http/current/client-side/request-level.html#example - Charlie 木匠

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接