所以我正在编写一个数据包嗅探应用程序。基本上我希望它嗅探 tcp 会话,然后解析它们以查看它们是否是 http,如果它们是,以及它们是否具有正确的内容类型等,将它们作为文件保存在我的硬盘上。
因此,为此,我希望它高效。由于当前的 http 库是基于字符串的,而且我将处理大文件,而且我真的只需要解析 http 响应,因此我决定在 attoparsec 中推出我自己的库。
当我完成我的程序时,我发现当我解析一个包含 wav 文件的 9 meg http 响应时,当我分析它时,它在尝试解析 http 响应的主体时分配了一个内存. 当我查看 HTTP.prof 时,我看到了一些行:
httpBody Main 362 1 0.0 0.0 93.8 99.3 取 Data.Attoparsec.Internal 366 1201 0.0 0.0 93.8 99.3 takeWith Data.Attoparsec.Internal 367 3603 0.0 0.0 93.8 99.3 需求输入数据.Attoparsec.Internal 375 293 0.0 0.0 93.8 99.2 提示 Data.Attoparsec.Internal 378 293 0.0 0.0 93.8 99.2 +++ 数据.Attoparsec.Internal 380 586 93.8 99.2 93.8 99.2
如您所见,在 httpbody 中的某处,take 被调用了 1201 次,导致 500+ (+++) 个字节串连接,这导致了荒谬的内存分配量。
这是代码。N 只是 http 响应的内容长度,如果有的话。如果没有,它只是试图夺走一切。
我希望它返回一个包含 1000 个左右字符字节串的惰性字节串,但即使我将它更改为只取 n 并返回一个严格的字节串,它仍然有这些分配(并且它使用 14 gig 的内存)。
httpBody n = do
x <- if n > 0
then AC.take n
else AC.takeWhile (\_ -> True)
if B.length x == 0
then return Nothing
else return (Just x)
我正在阅读一个做组合的人的博客,他遇到了同样的问题,但我从未听说过解决方案。有没有人遇到过这个问题或找到解决方案?
编辑:好的,好吧,我一整天都把它放在一边,什么也没得到。在研究了这个问题之后,我认为没有办法在不向 attoparsec 添加惰性字节串访问器的情况下做到这一点。我还查看了所有其他库,它们要么缺少字节串,要么缺少其他东西。
所以我找到了一个解决方法。如果您考虑一个 http 请求,它会出现标题、换行符、换行符、正文。由于主体是最后一个,并且解析返回一个元组,其中包含您解析的内容和剩余的字节串,我可以跳过在 attoparsec 中解析主体,而是直接从剩下的字节串中提取主体。
parseHTTPs bs = if P.length results == 0
then Nothing
else Just results
where results = foldParse(bs, [])
foldParse (bs,rs) = case ACL.parse httpResponse bs of
ACL.Done rest r -> addBody (rest,rs) r
otherwise -> rs
addBody (rest,rs) http = foldParse (rest', rs')
where
contentlength = ((read . BU.toString) (maybe "0" id (hdrContentLength (rspHeaders http))))
rest' = BL.drop contentlength rest
rs' = rs ++ [http { rspBody = body' }]
body'
| contentlength == 0 = Just rest
| BL.length rest == 0 = Nothing
| otherwise = Just (BL.take contentlength rest)
httpResponse = do
(code, desc) <- statusLine
hdrs <- many header
endOfLine
-- body <- httpBody ((read . BU.toString) (maybe "0" id (hdrContentLength parsedHeaders)))
return Response { rspCode = code, rspReason = desc, rspHeaders = parseHeaders hdrs, rspBody = undefined }
它有点乱,但最终它运行得很快,并且只分配了我想要的。所以基本上你折叠收集http数据结构的字节串,然后在集合之间,我检查我刚刚得到的结构的内容长度,从剩余的字节串中提取适当的数量,然后如果还有任何字节串就继续。
编辑:我实际上完成了这个项目。奇迹般有效。我没有正确地阴谋化,但如果有人想查看整个源,你可以在https://github.com/onmach/Audio-Sniffer找到它。