在scala中使用akka流的NNTP客户端

在scala中使用akka流的NNTP客户端,scala,akka-stream,Scala,Akka Stream,我正在尝试实现一个NNTP客户端,它将命令列表流式传输到服务器并解析返回的结果。我面临着几个问题: NNTP协议没有可用于框显结果的“唯一”分隔符。有些命令返回多行响应。如何处理流 如何“映射”随服务器响应发出的命令,并在发送下一个命令之前等待服务器响应结束?(此处与节流无关) 如何在断开连接时停止流处理?(实际上,程序永远不会返回) 以下是我当前的实现: import akka.stream._ import akka.stream.scaladsl._ import akka.{ No

我正在尝试实现一个NNTP客户端,它将命令列表流式传输到服务器并解析返回的结果。我面临着几个问题:

  • NNTP协议没有可用于框显结果的“唯一”分隔符。有些命令返回多行响应。如何处理流
  • 如何“映射”随服务器响应发出的命令,并在发送下一个命令之前等待服务器响应结束?(此处与节流无关)
  • 如何在断开连接时停止流处理?(实际上,程序永远不会返回)
以下是我当前的实现:

import akka.stream._
import akka.stream.scaladsl._

import akka.{ NotUsed, Done }
import akka.actor.ActorSystem
import akka.util.ByteString
import scala.concurrent._
import scala.concurrent.duration._
import java.nio.file.Paths
import scala.io.StdIn

import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Success
import scala.util.Failure


object AutomatedClient extends App {
  implicit val system = ActorSystem("NewsClientTest")
  implicit val materializer = ActorMaterializer()

  // MODEL //
  final case class Command(query: String)
  final case class CommandResult(
      resultCode: Int,
      resultStatus: String,
      resultList: Option[List[String]])
  final case class ParseException(message: String) extends RuntimeException

  // COMMAND HANDLING FUN //
  // out ->
  val sendCommand: Command => ByteString = c => ByteString(c.query + "\r\n")
  // in <-
  val parseCommandResultStatus: String => (Int, String) = s => 
    (s.take(3).toInt, s.drop(3).trim)
  val parseCommandResultList: List[String] => List[String] = l =>
    l.foldLeft(List().asInstanceOf[List[String]]){
      case (acc, ".") => acc
      case (acc, e) => e.trim :: acc
    }.reverse
  val parseCommandResult: ByteString => Future[CommandResult] = b => Future {
    val resultLines = b.decodeString("UTF-8").split("\r\n")
    resultLines.length match {
      case 0 => throw new ParseException("empty result")
      case 1 => 
        val (code, text) = parseCommandResultStatus(resultLines.head)
        new CommandResult(code, text, None)
      case _ =>
        val (code, text) = parseCommandResultStatus(resultLines.head)
        new CommandResult(code, text, Some(parseCommandResultList(resultLines.tail.toList)))
    }
  }

  // STREAMS //
  // Flows
  val outgoing: Flow[Command, ByteString, NotUsed] = Flow fromFunction sendCommand
  val incoming: Flow[ByteString, Future[CommandResult], NotUsed] = Flow fromFunction parseCommandResult
  val protocol = BidiFlow.fromFlows(incoming, outgoing)
  // Sink
  val print: Sink[Future[CommandResult], _] = Sink.foreach(f => 
    f.onComplete { 
      case Success(r) => println(r)
      case Failure(r) => println("error decoding command result")
    })
  // Source
  val testSource: Source[Command, NotUsed] = Source(List(
          new Command("help"),
          new Command("list"),
      new Command("quit")
  ))

  val (host, port) = ("localhost", 1119)
  Tcp()
    .outgoingConnection(host, port)
    .join(protocol)
    .runWith(testSource, print)

}
导入akka.stream_
导入akka.stream.scaladsl_
导入akka。{未使用,完成}
导入akka.actor.ActorSystem
导入akka.util.ByteString
导入scala.concurrent_
导入scala.concurrent.duration_
导入java.nio.file.path
导入scala.io.StdIn
导入scala.concurrent.ExecutionContext.Implicits.global
导入scala.util.Success
导入scala.util.Failure
对象自动化客户端扩展应用程序{
隐式val系统=ActorSystem(“NewsClientTest”)
隐式val-materializer=actormatarializer()
//模型//
最终案例类命令(查询:字符串)
最终案例类命令结果(
结果代码:Int,
结果状态:字符串,
结果列表:选项[列表[字符串]])
最后一个案例类ParseException(消息:String)扩展了RuntimeException
//指挥处理乐趣//
//出局->
val sendCommand:Command=>ByteString=c=>ByteString(c.query+“\r\n”)
//in(Int,String)=s=>
(s.起飞(3).起飞,s.降落(3).配平)
val parseCommandResultList:List[String]=>List[String]=l=>
l、 foldLeft(List().asInstanceOf[List[String]]{
案例(acc,“.”=>acc
外壳(acc,e)=>e.装饰::acc
}.反向
val parseCommandResult:ByteString=>Future[CommandResult]=b=>Future{
val resultLines=b.decodeString(“UTF-8”).split(“\r\n”)
结果行。长度匹配{
案例0=>抛出新的ParseException(“空结果”)
案例1=>
val(代码,文本)=parseCommandResultStatus(resultLines.head)
新CommandResult(代码、文本、无)
案例=>
val(代码,文本)=parseCommandResultStatus(resultLines.head)
新CommandResult(代码、文本、部分(parseCommandResultList(resultLines.tail.toList)))
}
}
//溪流//
//流动
val outgoing:Flow[Command,ByteString,NotUsed]=来自函数sendCommand的流
val incoming:Flow[ByteString,Future[CommandResult],NotUsed]=来自函数parseCommandResult的流
val协议=BidiFlow.fromFlows(传入、传出)
//下沉
val print:Sink[Future[CommandResult],3;]=Sink.foreach(f=>
f、 未完成{
案例成功率(r)=>println(r)
案例失败(r)=>println(“错误解码命令结果”)
})
//来源
val testSource:Source[命令,未使用]=源(列表(
新命令(“帮助”),
新命令(“列表”),
新命令(“退出”)
))
val(主机,端口)=(“本地主机”,1119)
Tcp()
.外部连接(主机、端口)
.加入(协议)
.runWith(测试源,打印)
}
以下是结果输出:

CommandResult(200,news.localhost NNRP Service Ready - newsmaster@localhost (posting ok),None)
CommandResult(100,Legal Commands,Some(List(article [<messageid>|number], authinfo type value, body [<messageid>|number], date, group newsgroup, head [<messageid>|number], help, last, list [active wildmat|active.times|counts wildmat], list [overview.fmt|newsgroups wildmat], listgroup newsgroup, mode reader, next, post, stat [<messageid>|number], xhdr field [range], xover [range], xpat field range pattern, xfeature useragent <client identifier>, xfeature compress gzip [terminator], xzver [range], xzhdr field [range], quit, 480 Authentication Required*, 205 Goodbye)))
CommandResult(200,news.localhost NNRP服务就绪-newsmaster@localhost(可以过帐),无)
CommandResult(100,合法命令,一些(列表(文章编号),authinfo类型值,正文编号,日期,组新闻组,标题编号,帮助,最后,列表[active wildmat | active.times | counts wildmat],列表[overview.fmt | newsgroups wildmat],列表组新闻组,模式读取器,next,post,stat[| number],xhdr字段[range],xover[range],xpat字段范围模式,xfeature useragent,xfeature compress gzip[终止符],xzver[范围],xzhdr字段[范围],退出,需要480身份验证*,205(可选)

我们可以看到,第二个CommandResult包含“list”命令和“quit”命令的结果。

感谢您发布此示例。我看到这个问题没有得到回答或解决,但帮助我实现了TCP双向处理。