Скачать указанный файл с потоками Акка?

Для следующего кода мне нужно, чтобы клиент отправил имя файла на сервер, чтобы сервер мог ответить клиенту на содержимое указанного файла. Это может сработать, если я не укажу имя файла, просто жесткое кодирование в серверной части, но как клиент скажет серверу, что ему нужен указанный файл?

Server.scala

package com.tst

import java.nio.file.Paths
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Tcp.{IncomingConnection, ServerBinding}
import akka.stream.scaladsl._
import akka.util.ByteString
import scala.concurrent.Future

object Server extends App {
  implicit val system = ActorSystem()
  implicit val ec = system.dispatcher
  implicit val materializer = ActorMaterializer()

  val connections: Source[IncomingConnection, Future[ServerBinding]] =
    Tcp().bind("127.0.0.1", 9989)
  connections runForeach { connection =>
    println(s"New connection from: ${connection.remoteAddress}")
    var fileName = ""

    // NOTE: here, the fileName = item can not affect the latter runWith,
    // I want to find solution
    val go = Flow[ByteString].via(connection.flow).map(_.utf8String).map {
      item => println(item); fileName = item
    }.runWith(FileIO.fromPath(Paths.get(fileName)), Sink.ignore)
  }
}

Client.scala

package com.tst

import java.nio.file.Paths
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl._
import akka.util.ByteString

object Client extends App {
  implicit val system = ActorSystem()
  implicit val ec = system.dispatcher
  implicit val materializer = ActorMaterializer()

  val connection = Tcp().outgoingConnection("127.0.0.1", 9989)
  val f = Source(List("D:/testinput.txt")).map(ByteString(_)).via(connection).
    runWith(FileIO.toPath(Paths.get("D:/testoutput.txt")))
  f.foreach {
    _ =>
      println("done")
      system.terminate()
  }
}

build.sbt

name := "streamtest"

version := "1.0"

scalaVersion := "2.11.8"

libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-stream" % "2.4.20"
)

1 ответ

Решение

На стороне сервера вы можете использовать handleWith метод для Connection в сочетании с Flow это берет в имени файла и производит строки файла:

import akka.stream.scaladsl.FileIO
import java.nio.file.Paths

val fileNameToContentsFlow : Flow[ByteString, ByteString, _] = 
  Flow[ByteString]
    .map(_.utf8String)
    .take(1L)
    .map(fileName => Paths.get(fileName))
    .flatMapConcat( filePath => FileIO.fromPath(filePath) )

Примечание: я добавил .take(1L) так что клиент может получить доступ только к 1 файлу на каждое соединение. Это может быть изменено для обработки нескольких файлов на одно соединение, но тогда необходимо будет вставить соответствующий разделитель между содержимым каждого отдельного файла.

Ваш код сервера будет немного изменен

connections runForeach { connection =>

  connection.handleWith(fileNameToContentsFlow)
}
Другие вопросы по тегам