Итак, я пишу приложение для обнюхивания пакетов. В основном я хотел, чтобы он нюхал для сеансов tcp, а затем анализировал их, чтобы узнать, являются ли они http, а если они есть, и если у них есть правильный тип контента и т.д., Сохраните их как файл на моем жестком диске.
Итак, с этой целью я хотел, чтобы это было эффективно. Поскольку текущая http-библиотека является основанной на строках, и я буду иметь дело с большими файлами, и мне действительно нужно было разобрать ответы на http, я решил опрокинуть свой собственный в attoparsec.
Когда я закончил свою программу, я обнаружил, что когда я разбирал 9-мегагерцовый ответ с WAV файлом в нем, когда я его профилировал, он выделял концерт памяти, когда он пытался разобрать тело ответ 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, take вызывается 1201 раз, вызывая 500+ (+++) конкатенации bytestrings, что вызывает абсурдный объем выделения памяти.
Вот код. N - это только длина содержимого ответа HTTP, если таковая имеется. Если его нет, он просто пытается взять все.
Я хотел, чтобы он возвращал ленивую байтовую последовательность из 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)
Я читал блог парнем, который делал combinatorrent, и у него была такая же проблема, но я никогда не слышал о разрешении. Кто-нибудь когда-либо сталкивался с этой проблемой раньше или нашел решение?
Изменить: Хорошо, я оставил это весь день и ничего не получил. После изучения проблемы я не думаю, что есть способ сделать это, не добавляя ленивый totestring accessor к 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.