使用Scala解析带有文件上载内容的多部分HTTP表单数据

时间:2012-03-19 10:19:24

标签: parsing scala

有很多multipart/form-data文件上传解决方案,但我找不到Scala的独立文件。

Play2将此功能作为框架的一部分,Spray也支持多部分表单数据。不幸的是,这些似乎都完全集成到其他工具集中(我可能在这里错了)。

我的服务器是使用Finagle(当前不支持多部分表单数据)开发的,如果可能的话,我想使用独立的lib或“自己动手”的解决方案。

这是典型的multipart / form-data消息:

--*****org.apache.cordova.formBoundary
Content-Disposition: form-data; name="value1"

First parameter content
--*****org.apache.cordova.formBoundary
Content-Disposition: form-data; name="value2"

Second parameter content
--*****org.apache.cordova.formBoundary
Content-Disposition: form-data; name="file"; filename="image.jpg"
Content-Type: image/jpeg

$%^&#$%^%#$
--*****org.apache.cordova.formBoundary--

在此示例中,*****org.apache.cordova.formBoundary是表单边界,因此分段上传包含2个文本参数和一个图像(为了清晰起见,我将图像数据连接在一起)。

如果有人比我更了解Scala,可以给我一些关于如何解析这些内容的简要介绍,我将非常感激。

首先,我想我会迅速将内容分成三部分:

data.split("\\Q--*****org.apache.cordova.formBoundary\\E") foreach println

但执行速度明显缓慢(更新 - 这是由于预热时间)。是否有更有效的方法来拆分零件?我的策略是将内容分成几部分,然后将部分拆分成子部分。这是一个糟糕的方法吗?我见过用状态机解决类似的问题?什么是好的功能方法。请记住,我正试图在尝试解决问题时学习一种适当的Scala方法。

更新

我真的认为这个问题的解决方案是Scala中的一两行。如果有人通过光滑的解决方案绊倒这个问题,请花时间记下来。根据我的理解,可以使用模式匹配,解析组合器,提取或简单地拆分字符串来解析此消息。我正在努力寻找解决此类问题的最佳方法,因为我正在进行的项目涉及大量自然语言解析,我需要编写自己的自定义解析工具。我对Scala有了很好的理解,但没有什么比专家的建议好。

这不仅仅是解决问题,而是找到解决此类问题的最佳方法(并且希望最简单)。

4 个答案:

答案 0 :(得分:1)

我很好奇你的“特别慢”的速度有多慢。我编写了以下简单的小函数来生成虚假消息:

def generateFakeMessage(n: Int) = {
  val rand = new scala.util.Random(1L)
  val maxLines = 100
  val maxLength = 100

  (1 to n).map(i =>
    "--*****org.apache.cordova.formBoundary\n" +
    "Content-Disposition: form-data; name=\"value%d\"\n\n".format(i) +
    (0 to rand.nextInt(maxLines)).map(_ =>
      (0 to rand.nextInt(maxLength)).map(_ => rand.nextPrintableChar).mkString
    ).mkString("\n")
  ).mkString("\n") + "\n--*****org.apache.cordova.formBoundary--"
}

接下来,我创建了一个相当大的消息用于测试:

val data = generateFakeMessage(10000)

它最终包含了超过五十万行。然后我尝试了你的正则表达式:

data.split("\\Q--*****org.apache.cordova.formBoundary\\E").size

它或多或少地瞬间返回。您可以稍微调整一下正则表达式,如果您的数据超过消息行的Iterable[String],则可以使用更简洁的方法,但我认为您不会获得更好的性能。一个手动状态机,用于解析一个大的String

答案 1 :(得分:0)

对于第一个建议,this question给出两个建议,一个使用状态机,另一个使用解析器组合器。我会特别注意使用解析器组合器的答案,因为这些提供了一种非常简单的方法来构建这种解析器。 Daniel的答案中提供的语法应该很容易适应您的情况。

此外,如果需要,您可以根据特定语法为Scala提供更具体的映射。丹尼尔在哪里:

  

def field =(fieldName<〜“:”)〜fieldBody< ~CRLF ^^ {case name~body =>名字 - >身体}

您可以将其替换为多个字段(contentType|contentDisposition|....)上的替换模式,并将每个字段分别映射到Scala对象中。

抱歉没有时间在这里写一个更详细的解决方案,但这应该有希望指出你正确的方向!

答案 2 :(得分:0)

我认为你的解决方案:

data.split("\\Q--*****org.apache.cordova.formBoundary\\E") foreach println

复杂度为O(n),是最好的,也是最简单的。正如特拉维斯先前所说,这种操纵并不慢。与使用多部分HTTP表单一样,您将不得不以某种方式解析它并且对O(n)做得更好似乎很棘手。

此外,由于split为您提供Iterable,因此对于任何匹配,治疗都非常完美...

答案 3 :(得分:0)

这可能是最糟糕的解决方案,并且无法以任何方式进行扩展,但为了快速从多部分请求中获取图像数据,我执行了以下操作(如果有人给出了更好的答案,我将取消标记我的答案):

// Take the request and split it into parts
var requestParts = request.content.toString(UTF_8).split("\\Q--*****org.apache.cordova.formBoundary\\E")
// Split the third part at the blank line
val imageParts = requestParts(3).split("\\n\\s*\\n")
// The part above the blank line is the header text
val imageHeader = imageParts(0)
// The part below the blank line is the image body
val imageBodyString = imageParts(1)

我稍后会尝试对此进行改进,但现在必须继续推进。另一天,另一个项目:-o