如何将Source[String, Unit]
连接到流媒体演员?
我认为来自https://gist.github.com/whysoserious/96050c6b4bd5fedb6e33的StreamingActor
的修改版本效果很好,但我很难连接各个部分。
鉴于source: Source[String, Unit]
和ctx: RequestContext
,我认为修改后的StreamingActor
应与actorRefFactory.actorOf(fromSource(source, ctx))
相符。
供参考,上面的要点:
import akka.actor._
import akka.util.ByteString
import spray.http.HttpEntity.Empty
import spray.http.MediaTypes._
import spray.http._
import spray.routing.{HttpService, RequestContext, SimpleRoutingApp}
object StreamingActor {
// helper methods
def fromString(iterable: Iterable[String], ctx: RequestContext): Props = {
fromHttpData(iterable.map(HttpData.apply), ctx)
}
def fromStringAndCharset(iterable: Iterable[String], ctx: RequestContext, charset: HttpCharset): Props = {
fromHttpData(iterable.map(HttpData.apply), ctx)
}
def fromByteArray(iterable: Iterable[Array[Byte]], ctx: RequestContext): Props = {
fromHttpData(iterable.map(HttpData.apply), ctx)
}
def fromByteString(iterable: Iterable[ByteString], ctx: RequestContext): Props = {
fromHttpData(iterable.map(HttpData.apply), ctx)
}
def fromHttpData(iterable: Iterable[HttpData], ctx: RequestContext): Props = {
Props(new StreamingActor(iterable, ctx))
}
// initial message sent by StreamingActor to itself
private case object FirstChunk
// confirmation that given chunk was sent to client
private case object ChunkAck
}
class StreamingActor(chunks: Iterable[HttpData], ctx: RequestContext) extends Actor with HttpService with ActorLogging {
import StreamingActor._
def actorRefFactory = context
val chunkIterator: Iterator[HttpData] = chunks.iterator
self ! FirstChunk
def receive = {
// send first chunk to client
case FirstChunk if chunkIterator.hasNext =>
val responseStart = HttpResponse(entity = HttpEntity(`text/html`, chunkIterator.next()))
ctx.responder ! ChunkedResponseStart(responseStart).withAck(ChunkAck)
// data stream is empty. Respond with Content-Length: 0 and stop
case FirstChunk =>
ctx.responder ! HttpResponse(entity = Empty)
context.stop(self)
// send next chunk to client
case ChunkAck if chunkIterator.hasNext =>
val nextChunk = MessageChunk(chunkIterator.next())
ctx.responder ! nextChunk.withAck(ChunkAck)
// all chunks were sent. stop.
case ChunkAck =>
ctx.responder ! ChunkedMessageEnd
context.stop(self)
//
case x => unhandled(x)
}
}
答案 0 :(得分:2)
我认为你使用StreamingActor
过分复杂了你试图解决的根本问题。此外,问题中的StreamingActor将为单个HttpResponse
生成多个HttpRequest
值,每个Chunk为1。这是低效的,因为您只需返回1个HttpReponse,其中HttpEntity.Chunked
作为数据流源的实体。
通用并发设计
演员是为州,例如保持连接之间的运行计数器。即便如此,Agent
还有很多基础,还有类型检查的额外好处(与Actor.receive不同,后者在运行时将死信邮箱转换为唯一的类型检查器)。
并发计算,而不是状态,应该用(按顺序)处理:
期货作为首要考虑因素:可组合,编译时安全检查,以及大多数情况下的最佳选择。
akka Streams:可组合,编译时间类型安全检查,非常有用但是由于方便的反压功能而产生了很多overhead。 Steams也是HttpResponse实体的形成方式,如下所示。
流式传输CSV文件
您的基本问题是如何使用Streams将csv文件流式传输到http客户端。您可以从创建数据源并将其嵌入HttpResponse开始:
def lines() = scala.io.Source.fromFile("DataFile.csv").getLines()
import akka.util.ByteString
import akka.http.model.HttpEntity
def chunkSource : Source[HttpEntity.ChunkStreamPart, Unit] =
akka.stream.scaladsl.Source(lines)
.map(ByteString.apply)
.map(HttpEntity.ChunkStreamPart.apply)
def httpFileResponse =
HttpResponse(entity = HttpEntity.Chunked(ContentTypes.`text/plain`, chunkSource))
然后,您可以为任何请求提供此响应:
val fileRequestHandler = {
case HttpRequest(GET, Uri.Path("/csvFile"), _, _, _) => httpFileResponse
}
然后将fileRequestHandler嵌入到服务器路由逻辑中。