Dav*_*aly 19 parsing haskell attoparsec
所以我正在编写一个数据包嗅探应用程序.基本上我想让它嗅探tcp会话,然后解析它们是否是http,如果它们是,如果它们有正确的内容类型等,请将它们保存为我的硬盘上的文件.
所以,为此,我希望它有效率.由于当前的http库是基于字符串的,我将处理大文件,而我只需要解析http响应,我决定在attoparsec中自己编写.
当我完成我的程序时,我发现当我使用其中的wav文件解析9 meg的http响应时,当我对其进行分析时,它在尝试解析http响应的主体时分配了一大堆内存.当我查看HTTP.prof时,我看到一些行:
httpBody Main 362 1 0.0 0.0 93.8 99.3
take 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
demandInput Data.Attoparsec.Internal 375 293 0.0 0.0 93.8 99.2
prompt Data.Attoparsec.Internal 378 293 0.0 0.0 93.8 99.2
+++ Data.Attoparsec.Internal 380 586 93.8 99.2 93.8 99.2
正如你所看到的,httpbody中的某个地方,被称为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)
Run Code Online (Sandbox Code Playgroud)
我正在阅读那个做组合工作的人的博客,他遇到了同样的问题,但我从来没有听说过解决方案.有没有人遇到过这个问题或找到解决方案?
编辑:好的,好吧,我把这一整天留下来,什么也没得到.在研究了这个问题之后,我认为没有一种方法可以在没有为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 }
Run Code Online (Sandbox Code Playgroud)
它有点乱,但最终它的工作速度很快,并且只能分配我想要的东西.所以基本上你折叠收集http数据结构的字节串,然后在集合之间,我检查我刚刚得到的结构的内容长度,从剩余的字节串中提取适当的数量,然后如果剩下任何字节串继续.
编辑:我实际上完成了这个项目.奇迹般有效.我没有正确的cabalized,但如果有人想查看整个源,你可以在https://github.com/onmach/Audio-Sniffer找到它.
这里的组合家伙:)
如果内存服务,attoparsec的问题是需要一次输入一点点,建立一个最终连接的惰性字节串.我的"解决方案"是自己滚动输入功能.也就是说,我从网络套接字获取attoparsec的输入流,我知道消息中需要多少字节.基本上,我分为两种情况:
消息很小:从套接字读取最多4k并一次吃掉一点字节串(字节串很快,我们在耗尽后扔掉4k).
消息是"大"(这里大的意思是大约16千字节的bittorrent说话):我们计算我们有多少4k块可以实现,然后我们只是请求底层网络套接字填充.我们现在有两个字节串, 4k块的剩余部分和大块.他们拥有所有数据,因此连接它们并解析它们就是我们所做的.
您可以优化连接步骤.
TL; DR版本:我在attoparsec之外处理它并手动循环以避免问题.
相关的组合提交是fc131fe24,参见
https://github.com/jlouis/combinatorrent/commit/fc131fe24207909dd980c674aae6aaba27b966d4
细节.