AKKA HTTP源流与期货

时间:2017-10-29 17:22:03

标签: scala akka akka-stream akka-http

我在Cassandra表中分割了一个大型视频文件。我正在尝试使用Source流式传输回流到API客户端。

我的服务代码如下所示,

def getShards(id: String, shards: Int) = {
  def getShardsInternal(shardNo: Int, shards: Future[Array[Byte]]): Future[Array[Byte]] = {
    if (shardNo == 0) shards
    else getShardsInternal(shardNo - 1, shards.flatMap(x => Database.ShardModel.find(id, shardNo)))
  }
  getShardsInternal(shards, Future.successful(Array()))
}

在我的AKKA HTTP路由中,我尝试从返回的未来构建Source,如下所示,

def getAsset = get {
  pathPrefix("asset") {
    parameters('id) { id =>
      complete {
        val f = mediaService.getMetadata(id).flatMap { x =>
          mediaService.getShards(id, x.shards)
        }
        Source.fromFuture(f)
      }
    }
  }
}

我不确定Source.fromFuture如何致力于响应。通过的未来基本上是一系列预期按顺序执行的平面映射期货。但是,我不相信这会作为一个分块的字节流返回给客户端。

对此的任何指示都将受到高度赞赏。

编辑1 我一直试图通过以下方式进一步缩小范围,

get {
  pathPrefix("asset") {
    parameters('id) { id =>
      complete {
        Source.fromFuture {
          Future.successful("Hello".getBytes()).flatMap(x => Future.successful("World".getBytes()))
        }
      }
    }
  }
}

我原本希望这会回来

[72,101,108,108,111,32,87,111,114,108,100]

但是,我只能得到下一个结果,

[[87,111,114,108,100]]

亲切的问候 Meeraj

1 个答案:

答案 0 :(得分:2)

将您的Source[Array[Byte], NotUsed]转换为Source[ByteString, NotUsed],并将HttpEntityContentTypes一起使用:

import akka.util.ByteString

def getAsset = get {
  pathPrefix("asset") {
    parameters('id) { id =>
      val f = mediaService.getMetadata(id).flatMap { x =>
        mediaService.getShards(id, x.shards)
      }
      val source = Source.fromFuture(f).map(ByteString.apply)
      complete(HttpEntity(ContentTypes.`application/octet-stream`, source))
    }
  }
}

我在这里以application/octet-stream为例。由于您要对视频进行流式传输,因此您可能需要将ContentType.Binary与适当的media type一起使用。例如:

complete(HttpEntity(ContentType.Binary(MediaTypes.`video/mpeg`), source))    

解决您的评论和更新,您似乎希望在getShards中连接期货的结果:正如您发现的那样,flatMap没有这样做。请改用Future.reduceLeft

def getShards(id: String, shards: Int): Future[Array[Byte]] = {
  val futures = (1 to shards).map(Database.ShardModel.find(id, _))
  Future.reduceLeft(futures)(_ ++ _)
}

或者,您可以重新定义getShards以返回Future[List[Array[Byte]]],然后使用Source创建flatMapConcat,而不是将结果连接到单个数组中:

def getShards(id: String, shards: Int): Future[List[Array[Byte]]] = {
  val futures = (1 to shards).map(Database.ShardModel.find(id, _)).toList
  Future.sequence(futures)
}

def getAsset = get {
  pathPrefix("asset") {
    parameters('id) { id =>
      val f = mediaService.getMetadata(id).flatMap { x =>
        mediaService.getShards(id, x.shards)
      }
      val source =
        Source.fromFuture(f)
              .flatMapConcat(Source.apply)
              .map(ByteString.apply)
      complete(HttpEntity(/* a content type */, source))
    }
  }
}