だから私はパケット盗聴アプリを書いています。基本的には、tcp セッションをスニッフィングし、それらを解析して http であるかどうかを確認し、http であるかどうか、適切なコンテンツ タイプを持っているかどうかなどをハード ドライブにファイルとして保存したかったのです。
だから、そのためには、効率的にしたかったのです。現在の http ライブラリは文字列ベースであり、大きなファイルを扱うことになるため、本当に必要なのは http 応答を解析することだけだったので、attoparsec で独自のものを作成することにしました。
プログラムを終了したとき、wav ファイルを含む 9 メガの http 応答を解析しているときに、プロファイリングしたときに、http 応答の本文を解析しようとしているときに、1 ギガのメモリを割り当てていることがわかりました。 . HTTP.prof を見ると、いくつかの行が表示されます。
httpBody メイン 362 1 0.0 0.0 93.8 99.3 Data.Attoparsec.Internal 366 1201 0.0 0.0 93.8 99.3 を取る takeWithData.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 プロンプト 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 内のどこかで take が 1201 回呼び出され、バイト文字列が 500 回以上 (+++) 連結され、途方もない量のメモリが割り当てられます。
これがコードです。N は、http 応答がある場合、そのコンテンツの長さです。1 つも存在しない場合は、すべてを取得しようとします。
1000 文字程度のバイト文字列の遅延バイト文字列を返すようにしたかったのですが、n だけを取得して厳密なバイト文字列を返すように変更しても、それらの割り当てはまだ残っています (そして 14 ギガのメモリを使用します)。
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で見つけることができます。