Парсер в Nimbus Note, или как мы решали проблему «чистого» HTML

    Одна из ключевых возможностей Nimbus Note — это сохранение и/или редактирование заметок в виде html-документа. И заметки эти создаются/редактируются в браузере или на мобильных устройствах. После чего — отправляются на сервер. А как подсказывает профессиональная паранойя — информации пришедшей от пользователя доверять нельзя. Т.к. там может быть всё что угодно: XSS, документ, превращающий вёрстку в мечту абстракциониста или вообще ни разу не текст. Следовательно, данные пришедшие от пользователя нуждаются в предварительной обработке. В этой статье я опишу некоторые особенности нашего решения данной проблемы.



    Казалось бы что тут сложного? Добавить какой-либо html-purifyer перед сохранением и всё. Да, верно, так можно было бы сделать, если бы не некоторые обстоятельства:

    • в одной заметке текста может быть много (несколько мегабайт);
    • предполагается значительное число одновременных запросов на сохранение изменений;
    • запросы на сохранение предположительно будут производится из разных частей, написанных на разных языках;
    • после обработки текста и перед сохранением возможны дополнительные проверки;
    • после обработки нужно сохранить внешний вид заметки максимально близким к изначальному (в идеале — внешний вид не должен совсем измениться);
    • вёрстка страницы при отображении сохранённой заметки не должна «страдать»;
    • невозможно использовать iframe.


    Первые три пункта однозначно требуют, наличия решения, работающего отдельно от основного кода. Четвёртый же исключает использование очередей (RabbitMQ к примеру) или, что равноценно, приводит к необходимости нетривиальных решений при их использовании.

    И, наконец, последние три пункта требуют глубокой обработки вёрстки с учётом того, что изначально она скорее всего не валидна («левые» и/или незакрытые теги, атрибуты, значения). К примеру если ширина любого элемента выставлена в 100500, то это значение не попадает в определение «допустимого» и должно быть удалено либо заменено (в зависимости от настроек).

    Все вышеупомянутые доводы привели к тому, что мы решили писать свой велосипед парсер/валидатор. В качестве языка выбрали python. Причиной послужило то, что основной проект написан на этом языке и, конечно же, свою роль сыграли эстетические предпочтения.

    Дабы не писать совсем уж всё с нуля, было принято решение упростить себе жизнь и задействовать какой-либо лёгкий фреймворк. Выбор пал на tornado, т. к. с ним у нас уже был опыт работы.

    Исходя из соображений масштабируемости добавили в систему nginx в качестве балансировщика нагрузки. Такая структура позволяет в довольно широких пределах наращивать обрабатывающие мощности простым добавлением инстансов парсера. А наличие timeout-а у клиента на время ожидания ответа от парсера позволяет задать максимальное время ожидания, которое всё ещё не выйдет из зоны комфорта для пользователей (не вызовет ощущения, что «всё висит»).

    В качестве движка html-парсера поначалу выбрали lxml. Хороший, быстрый, написанный на C парсер. И всё бы с ним хорошо, если бы не пара «сюрпризов».

    Во-первых в процессе работы во всей «красе» проявился такой известный факт, как интерпретация lxml библиотекой html-докуметов как «битых» xml-ок. Эта особенность, поначалу не вызывавшая опасений, начала продуцировать всё возрастающее количество «костылей». Так, к примеру, lxml настойчиво считал, что «» — одинарный тег и исправно проводил следующее преобразование « => <span />».

    Впрочем, с «костылями» можно было бы мириться, если бы не вылезло «во-вторых». При тестовом прогоне на копии реальных данных парсер стабильно вылетал по «Segmentation Fault». Что было этому причиной — неизвестно. Т.к. «вылет» гарантированно происходил после обработки примерно полутысячи записей. Вне зависимости от их содержимого (выборку производили из разных мест в таблице).

    Таким образом, набив некоторое количество «шишек», остановились на связке «Beautiful Soup», «html5lib» плюс свои костыли наработки.

    После этого решения уже почти начало казаться «вот оно, счастье». И длилось это счастье ровно до того момента, пока не попалась на глаза обработанная парсером страничка msn.com. Примечательными особенностями этой странички оказались активное, с выдумкой, использование атрибута «type» для тега «input» и любовь их верстальщиков к «position: absolute;».Так как проблема была локализирована, то решить её было сравнительно несложно — поправить конфиги, чуток код и, конечно же, написать тесты, покрывающие найденные тонкие места.

    Теперь мы не просто абстрактно уверены, что множество страниц в сети содержат невалидный html, но ждём когда же придёт новый «сюрприз». Ждём, пытаемся принимать превентивные меры и знаем, что однажды мы увидим её, прошедшую все фильтры, все ухищрения. Увидим страницу являющуюся продуктом горячечного бреда абстракциониста…
    FVD Media
    17.80
    Company
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 2

      0
      Так, к примеру, lxml настойчиво считал, что «» — одинарный тег и исправно проводил следующее преобразование « => <span />».


      Парсер здесь съел какие-то символы?
        0
        Это скорее хабр что то сделал.

        Only users with full accounts can post comments. Log in, please.