如何在两个字节的分隔符处拆分akka ByteString

时间:2017-12-29 21:10:24

标签: websocket akka

我正在创建自己的WebSocket协议,并且认为有一个文本键/值标题部分,以两个连续的换行符结束,后跟一个二进制尾部。

事实证明,将ByteString分成两半(在两个换行符处)真的很乏味。没有内置的.split方法。并且没有.indexOf来查找二进制指纹。

你会用什么?我有更简单的方法来构建这样的协议吗?

参考文献:

使用akka-http 10.1.0-RC1,akka 2.5.8

3 个答案:

答案 0 :(得分:1)

一种方法是首先从ByteString的indexedSeq创建滑动对,然后使用分隔符对的已识别索引拆分ByteString,如下例所示:

import akka.util.ByteString

val bs = ByteString("aa\nbb\n\nxyz")
// bs: akka.util.ByteString = ByteString(97, 97, 10, 98, 98, 10, 10, 120, 121, 122)

val delimiter = 10

// Create sliding pairs from indexedSeq of the ByteString
val slidingList = bs.zipWithIndex.sliding(2).toList
// slidingList: List[scala.collection.immutable.IndexedSeq[(Byte, Int)]] = List(
//   Vector((97,0), (97,1)), Vector((97,1), (10,2)), Vector((10,2), (98,3)),
//   Vector((98,3), (98,4)), Vector((98,4), (10,5)), Vector((10,5), (10,6)),
//   Vector((10,6), (120,7)), Vector((120,7), (121,8)), Vector((121,8), (122,9))
// )

// Get indexes of the delimiter-pair
val dIndex = slidingList.filter{
  case Vector(x, y) => x._1 == delimiter && y._1 == delimiter
}.flatMap{
  case Vector(x, y) => Seq(x._2, y._2)
}

// Split the ByteString list
val (bs1, bs2) = ( bs.splitAt(dIndex(0))._1, bs.splitAt(dIndex(1))._2.tail )
// bs1: akka.util.ByteString = ByteString(97, 97, 10, 98, 98)
// bs2: akka.util.ByteString = ByteString(120, 121, 122)

答案 1 :(得分:0)

我想出了这个。 Haven尚未在实践中对其进行测试。

@tailrec
def peelMsg(bs: ByteString, accHeaderLines: Seq[String]): Tuple2[Seq[String],ByteString] = {

  val (a: ByteString, tail: ByteString) = bs.span(_ != '\n')
  val b: ByteString = tail.drop(1)

  if (a.isEmpty) {    // end marker - empty line
    Tuple2(accHeaderLines,b)
  } else {
    val acc: Seq[String] = accHeaderLines :+ a.utf8String    // append
    peelMsg(b,acc)
  }
}

val (headerLines: Seq[String], value: ByteString) = peelMsg(bs,Seq.empty)

答案 2 :(得分:0)

我的代码,现在:

// Find the index of the (first) double-newline
//
val n: Int = {
  val bsLen: Int = bs.length

  val tmp: Int = bs.zipWithIndex.find{
    case ('\n',i) if i<bsLen-1 && bs(i+1)=='\n' => true
    case _ => false
  }.map(_._2).getOrElse{
    throw new RuntimeException("No delimiter found")
  }
  tmp
}

val (bs1: ByteString, bs2: ByteString) = bs.splitAt(n)    // headers, \n\n<binary>

受@ leo-c的回答影响,但使用普通.find代替滑动窗口。实现了由于ByteString允许随机访问,我可以将流式搜索与该条件结合起来。