【问题标题】:Efficient algo / way to parse (without any framework) a multipart/form-data request without reading everything to memory?解析(没有任何框架)多部分/表单数据请求而不将所有内容读入内存的有效算法/方法?
【发布时间】:2017-02-15 15:45:18
【问题描述】:

我的问题很简单:我想在大文件上传到达时将其写入磁盘。我有两个大文件通过同一个multipart/form-data 表单上传。如何检测文件的结尾,换句话说,如何检测到达字节中间的边界------WebKitFormBoundaryuFPBAbBHzPMrZn8g

拥有被上传文件的长度可以完全解决这个问题,但是这个信息不是由 http 请求给出的(只是完整的内容长度,而不是被上传的单个文件的长度)。

那么当我将字节写入磁盘时,检测边界的逻辑/策略/算法是什么。当然,我不想写边界,认为它是文件的一部分。我必须检测并停止写入磁盘。请注意,在开始写入磁盘之前,我无法将整个文件加载到内存中。这样问题就容易多了。

这是一个包含两个文件的 multipart/form-data 的格式:

发布/HTTP/1.1 主机:本地主机:8000 连接:保持活动 内容长度:362 缓存控制:max-age=0 产地:无 升级不安全请求:1 用户代理:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36 内容类型:multipart/form-data;边界=----WebKitFormBoundaryuFPBAbBHzPMrZn8g 接受:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 接受编码:gzip,放气 接受语言:en-US,en;q=0.8,pt;q=0.6 ------WebKitFormBoundaryuFPBAbBHzPMrZn8g 内容处置:表单数据;名称=“文件1”;文件名="二进制.dat" 内容类型:应用程序/八位字节流 aωb ------WebKitFormBoundaryuFPBAbBHzPMrZn8g 内容处置:表单数据;名称=“文件2”;文件名="二进制.dat" 内容类型:应用程序/八位字节流 aωb ------WebKitFormBoundaryuFPBAbBHzPMrZn8g--

【问题讨论】:

    标签: forms algorithm http parsing file-upload


    【解决方案1】:

    第一个非常简单的方法可能适合您的需求,您可以使用内存库函数来查找和移动数据,如下所示:

    假设您的边界是 N + 1 个字节(在您的情况下,数据为 40,N 为 39),分配一个大于您的签名的任何大小的缓冲区,然后在缓冲区中首次接收缓冲区大小并输入如下所述处理数据的循环,直到您没有更多数据要接收:

    1 - 在缓冲区中查找签名。如果你找到它,那么你就完成了你的第一个文件。将字节保存到查找点并关闭第一个文件。然后打开第二个文件,将查找点末尾的字节向上移动到缓冲区末尾到缓冲区的开头,接收字节完成缓冲区并继续循环。

    2 - 如果在缓冲区中找不到数据,则将所有数据写入文件,直到 (buffer + sizeof(buffer) - N - 1),将最后 N 个字节移动到缓冲区的开头,接收剩余的字节以填满缓冲区并继续循环。

    一种不移动数据但需要您检查每个字节的更简洁的方法是执行以下操作:

    1 - 分配任意大小的缓冲区。

    2 - 将匹配计数器设置为零。

    3 - 设置一个包含边界数据字节的 boundingData 数组。

    4 - 输入执行以下操作的循环

    5 - 接收缓冲区中的字节,直到缓冲区大小或接收端

    6 - 进入另一个循环,检查每个字节的接收数据范围,如下所示:

    如果正在检查的字节等于 boundingData[matchCounter] 然后增加计数器并检查它是否达到了 boundingData 的长度。如果确实如此,则关闭您的文件,打开下一个文件并将 matchCounter 设置为零。

    否则,如果 matchCounter 不为零,则 write(boundingData, matchCount) 然后将检查的字节写入您的文件。

    当您完成缓冲区后,返回第 5 步,直到您没有更多数据要接收。

    【讨论】:

    • 我想知道谁是编写这个 multipart/form-data 协议的聪明人。这使得解析变得非常困难。人们对有效载荷协议,有效载荷长度+有效载荷有什么看法?
    • 如果你有幸在文件数据中间有有效载荷会发生什么? O.o 几率是否如此之低以至于人们认为这是不可能的?
    【解决方案2】:

    João 提供了两个很好的建议。两者都解释了边界部分在一个缓冲区中而部分在另一个缓冲区中的情况。请注意,无论您如何实现此解析器,您的代码最终都会将读取的每个字节与边界字符至少比较一次。恕我直言,最好的设计是确保您只比较一次。

    根据 RFC1341 (https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html),最大边界长度 (N) 为 70。这不包括正文中每个边界实例之前的前导“--”,也不包括标记正文结束的最终边界之后的“--”。该标准还告诉我们在 Content-Type 规范中删除边界末尾的任何尾随空格,尽管我还没有找到将任何尾随空格放在那里的浏览器。

    我以与 João 的第二个建议非常相似的方式实现了我的 multipart/form-data 解析器,除了如果 Content-Disposition 包含文件名,我的解析器只存储与边界匹配的字符。如果在进行部分匹配时接收到与下一个边界字符不匹配的字符,我会将存储的字符写入文件并将缓冲区索引重置为 0。如果文件是唯一可以接收的内容,我的解析器的缓冲区只需要与最大边界长度一样长。

    但实际上,可能会收到其他表单字段,例如“文本区域”,其值远超过 70 个字符。由于标准对其大小没有限制,因此表单字段可能会决定您的缓冲区大小而不是文件上传,这取决于您处理它们的方式以及目标系统提供的资源。我的实现将非文件数据存储在累积边界匹配的同一缓冲区中,并在匹配整个边界字符串后提取值。

    感谢 João 的深思熟虑的建议。如果 stackoverflow 允许,我会为你竖起大拇指。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-02-01
      • 2016-12-14
      • 2020-06-17
      • 1970-01-01
      • 1970-01-01
      • 2019-06-01
      • 2016-01-12
      • 1970-01-01
      相关资源
      最近更新 更多