scalaz-stream:如何处理"标题" (第一块)以不同的方式休息?

时间:2015-03-03 11:20:53

标签: scala scalaz-stream

  

上下文:我正在尝试使用输出Process1[ByteVector, spray.http.HttpResponsePart]编写ChunkedResponseStart(bytes), MessageChunk(bytes), MessageChunk(bytes), ..., ChunkedResponseEnd。我还没有完全围绕scalaz-stream及其词汇。

如何编写能够以不同方式处理第一个n块的进程?

我想出了这个(字符串作为例子):

val headerChunk = process1.chunk[String](5).map(_.reduce(_ + _))

val headerChunkAndRest: Process1[String, String] =
  headerChunk.take(1) ++ process1.id

io.linesR(Files.newInputStream(Paths.get("testdata/fahrenheit.txt")))
  .pipe(headerChunkAndRest)
  .to(io.stdOutLines)
  .run.run

什么是惯用的,可能是一种通常可编写的方式来编写headerChunkAndRest

1 个答案:

答案 0 :(得分:4)

一般注意事项

有几种方法可以做到这一点,这很大程度上取决于您的需求细节。您可以使用以下作为scalaz-streams一部分的辅助方法:

  1. foldWithIndex这为您提供了块的当前索引作为数字。您可以根据该指数进行区分
  2. zipWithState您可以添加从一个方法调用到下一个方法的状态,并使用此状态来跟踪您是否仍在解析标题,或者是否已到达正文。在下一步中,您可以使用此状态来处理不同的标题和正文
  3. repartition使用此选项可将所有标题和所有正文元素组合在一起。然后,您可以在下一步中处理它们。
  4. zipWithNext此函数始终显示与当前元素分组的上一个元素。当您从标题切换到正文并做出相应反应时,您可以使用它来检测。
  5. 可能你应该重新思考,你真正需要什么。对于您的问题,它将是zipwithIndex,然后是map。但如果您重新考虑问题,可能会以repartitionzipWithState结束。

    示例代码

    让我们举一个简单的例子:一个HTTP客户端,它将HTTP头元素与主体分开(HTTP,而不是HTML)。在标题中,像身体中的cookie一样是真正的“内容”,如图像或HTTP源。

    一个简单的HTTP客户端可能如下所示:

    import scalaz.stream._
    import scalaz.concurrent.Task
    import java.net.InetSocketAddress
    import java.nio.channels.AsynchronousChannelGroup
    
    implicit val AG = nio.DefaultAsynchronousChannelGroup
    
    def httpGetRequest(hostname : String, path : String = "/"): Process[Nothing, String] =
      Process(
        s"GET $path HTTP/1.1",
        s"Host: $hostname",
        "Accept: */*",
        "User-Agent: scalaz-stream"
      ).intersperse("\n").append(Process("\n\n"))
    
    def simpleHttpClient(hostname : String, port : Int = 80, path : String = "/")(implicit AG: AsynchronousChannelGroup) : Process[Task, String] =
      nio.connect(new InetSocketAddress(hostname, port)).flatMap(_.run(httpGetRequest(hostname, path).pipe(text.utf8Encode))).pipe(text.utf8Decode).pipe(text.lines())
    

    现在我们可以使用此代码将标题行与其余标题分开。在HTTP中,标题以行结构。它通过空行与身体分开。首先,让我们计算标题中的行数:

    val demoHostName="scala-lang.org" // Hope they won't mind...
    simpleHttpClient(demoHostName).zipWithIndex.takeWhile(! _._1.isEmpty).runLast.run
    // res3: Option[(String, Int)] = Some((Content-Type: text/html,8))
    

    当我跑步时,标题中有8行。我们首先定义一个枚举,然后对响应的各个部分进行分类:

    object HttpResponsePart {
      sealed trait EnumVal
      case object HeaderLine extends EnumVal
      case object HeaderBodySeparator extends EnumVal
      case object Body extends EnumVal
      val httpResponseParts = Seq(HeaderLine, HeaderBodySeparator, Body)
    }
    

    然后让我们使用zipWithIndex加上map来对响应的各个部分进行分类:

    simpleHttpClient(demoHostName).zipWithIndex.map{
      case (line, idx) if idx < 9 => (line, HeaderLine)
      case (line, idx) if idx == 10 => (line, HeaderBodySeparator)
      case (line, _) => (line, Body)
    }.take(15).runLog.run
    

    对我来说,这很好用。但是,当然,标题行的数量可以随时更改,恕不另行通知。使用一个考虑响应结构的非常简单的解析器会更加健壮。为此,我使用zipWithState

    simpleHttpClient(demoHostName).zipWithState(HeaderLine : EnumVal){
      case (line, HeaderLine) if line.isEmpty => HeaderBodySeparator
      case (_, HeaderLine) => HeaderLine
      case (_, HeaderBodySeparator) => Body
      case (line, Body) => Body
    }.take(15).runLog.run
    

    您可以看到,两种方法都使用类似的结构,两种方法都应该导致相同的结果。好的是,这两种方法都很容易重复使用。您可以换掉源,例如用文件,不必改变任何东西。与分类后的处理相同。 .take(15).runLog.run在两种方法中完全相同。