Архив за Октябрь 2010

  • Идея для сниффинга браузеров

    Возникла одна идея, как можно скрестить клиентское и серверное «вынюхивание» браузера.

    Как это делается сейчас? На сервере мы смотрим на заголовок User Agent и определяем, что это за браузер. Но этот заголовок можно легко подменить (причём необязательно это делать в самом браузере, это может сделать прокси-сервер), поэтому такая проверка совсем ненадёжна. На клиентской стороне у нас гораздо больше возможностей: мы можем не только проверить User Agent браузера, но и его возможности (например, поддержку определённых CSS и JS свойств), что даст нам более точные характеристики браузера.

    Собственно, сама идея как раз заключается в том, чтобы скрестить эти два способа: определить с помощью CSS и JS характеристики браузера и в нужный момент сообщить их на сервер. Основа алгоритма — Chunked Transfer Encoding, позволяющий передать ответ (например, HTML-документ) не целиком, а кусками. Итак, сам алгоритм:

    1. Когда пользователь запрашивает HTML-страницу, отдаём ему первый фрагмент (chunk), в котором содержится sniffing-код. После этого соединение удерживается на некоторое время, чтобы отдать второй фрагмент документа.
    2. Браузер, получив первый фрагмент, тут же его парсит и пытается отобразить.
    3. В этом первом фрагменте у нас sniffing-код, результатом работы которого является CSS-свойство background-image, применяемое к элементу <html>.
    4. Путь к этой фоновой картинке является уникальным для каждого браузера и так уж получилось, что как только мы применили это свойство элементу, браузер открывает второе соединение и тут же пытается загрузить эту картинку.
    5. Сервер, получив запрос к нашей «картинке» понимает, каким браузером мы зашли и отдаёт вторую часть ответа в первом соединении.
    6. Если обращения к «картинке» не было (например, зашли поисковым роботом), то через какой-нибудь небольшой промежуток времени отдаём стандартный ответ.
    7. После первого «вынюхивания» можно поставить куку, чтобы потом сразу отдавать нужный ответ.

    В итоге получаем следующие преимущества:

    • Нет никаких редиректов: ответ о типе браузера получаем в одном соединении (полезно для SEO).
    • Очень сложно обмануть такой сниффер: можно до посинения менять строку User Agent, но результат останется прежним. Гугл сможет эффективней банить Оперу.
    • Более сложные проверки: можно не просто определять название браузера, но и его возможности, благодаря CSS Media Queries.

    Для желающих поэкспериментировать я написал демонстрацию этого подхода на node.js. К сожалению, у меня на VPS не хватает памяти, чтобы собрать node.js, поэтому предлагаю читателю самому его установить и проверить у себя. Если кто выложит это в онлайн — буду премного благодарен. Выложили: http://tbms.ru:8126/ (спасибо Николаю Митину).

    Полезные уроки

    Пока писал эту демонстрацию, вынес для себя много ценной информации:

    1. Первый чанк должен быть достаточно большим, чтобы все браузеры смогли его применить. Самый большой лимит — у движка Webkit: около 2 КБ. Поэтому приходится «добивать» первый фрагмент пробелами, чтобы всё работало как надо.
    2. Webkit очень плохо ведёт себя при переопределении CSS-свойств. Например:
      html { background-image:url(image1.png); }
      html { background-image:url(image2.png); }
      		

      В этом случае Webkit (и десктопный, и мобильный) загрузит обе картинки, хотя понятно, что нужна только последняя. Это ещё одно подтверждение, что мобильные версии сайта лучше делать отдельным проектом, а не вставлять через CSS Media Queries. Иначе попытки сократить траффик заменой больших фоновых картинок на маленькие приведёт к обратному эффекту. Хотя тут нужно больше исследований, возможно, это издержки работы с локальным сервером.

    3. Firefox не отработает первый чанк до тех пор, пока не получит элемент <body>. Это довольно серьёзный недостаток в том случае, если вы захотите выдавать уникальные стили и скрипты для браузера в секции <head>. С другой стороны, если послать <script>alert(1)</script>, то чанк отработает нормально, даже без <body>. Что наталкивает на мысль о существовании неких триггеров, которые заставят браузер сделать то, что нам нужно, осталось только найти наиболее безопасный.

    Повторюсь, что это только идея, а не призыв к действию. Буду рад, если кто-то на её основе сделает что-то крутое и интересное.

  • XSL Tracer

    Я, наконец-то, более-менее довёл до ума свой новый проект, которым хочу поделиться с народом.

    Проект называется XSL Tracer. Его задача заключается в том, чтобы ответить на простой вопрос: откуда после XSL-трансформации пришёл тот или иной элемент? Как это работает можно увидеть на демо-странице. Там отображается результат XSL-преобразования — обычный HTML-документ. Кликнув по любому элементу можно получить подробную информацию о нём:

    • XSL: шаблон, где выводится элемент.
    • Context: контекстный XML (на который сработал матч, в случае xsl:apply-templates). Сам элемент выводится урезанным (только 10 внутренних тэгов), чтобы в случае, если это будет корневой элемент, браузер не умер в муках (да и толку от полной структуры никакого).
    • Source: место в шаблоне, где непосредственно генерируется выбранный элемент. Очень удобно в случае, если делаете сopy-of результатирующей структуры (в примере это html-код, заданный внтутри переменной).
    • Call stack: стэк вызовов apply-template/call-template/apply-imports, через который дошли до вызова текущего шаблона
    • Inner calls: список внутренних вызовов шаблонов, которые по каким-то причинам нельзя отобразить (по крайней мере пока) в документе. Например, вызов шаблонов, выдающих атрибуты для выбранного элемента, или его текстовое содержимое. Список отображается под Call stack, и только в том случае, если такие вызовы есть.

    Демка нормально работает в Safari/Firefox/Chrome, Opera жутко тормозит на больших документах, а в IE даже не открывал.

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

    Как это работает

    Проект состоит из двух частей: бэкэнд и фронтэнд.

    На бэкэнде используется Saxon 6.5 (пока эта версия, так как в 9.2 лично у меня вылезло куча проблем с EXSLT). У него есть встроенный механизм трассировки, который по умолчанию генерирует результат в виде XML (такой файл на реальных проектах вырастает до нескольких мегабайт). Я написал свой трассировочный класс, который выводит данные в более компактном и лёгком JSON-формате, а также дополнительные данные вроде списка используемых в трансформации XSL и XML (подключённых через document()) документов. А также он делает дополнительные штуки вроде резолвинга result-tree фрагментов при вызове xsl:copy-of.

    Этот трассировщик генерирует JSON-документ и результатирующий документ (то есть результат предобразования) и собирает их в одном HTML-файле, который можно открывать в браузере.

    Дальше за работу берётся фронтэнд, который связывает трассировочные данные с результатом трансформа и обеспечивает всю работу интерфейса.

    Как запускать

    1. Должна быть установлена Java (версия особо не важна).
    2. Скачиваем Saxon 6.5
    3. Скачиваем трэйсер
    4. Генерируем трассировку вот такой командой в консоли:

    java -classpath /path/to/tracer.jar:/path/to/saxon.jar ru.imobilco.XSLTracer -to /path/to/trace-result.html /path/to/input.xml /path/to/template.xsl

    В случае успешной работы трэйсера сгенерируется файл trace-result.html, который можно сразу открывать в браузере (CSS и JS берутся с моего сервера). Если возникла ошибка во время трансформа, то файл будет сгенерирован, но вместо документа увидите нормальное сообщение об ошибке. В случае ошибки работы Java, увидите сообщение в консоли.

    Надо помнить, что Saxon очень чувствителен ко всяким всяким отхождениям от спецификации, поэтому то, что работало в Xalan/libxsl/где-то ещё может не сработать в Saxon. Но исправления, как правило, довольно мелкие и незначительные.

    Онлайн-версия

    Мой коллега Лёха Баранов написал сервлет, который позволяет запускать через веб, передавая ссылки на XML и XSL файлы. Для него был заведён специальный проект xmltools.ru. Работает просто: указываете ссылки на файлы и, если нужно, данные для http-авторизации. Далее сам трансформ находит все зависимые файлы, скачивает их и делает трансформ.

    Но с онлайн-версией не так всё просто. В частности, когда результат отдаётся фронтэнду, он тоже начинает скачивать внешние файлы через аякс. И, как вы уже, наверно, догадались, включается ограничение Same-Origin Policy, которое не позволяет загружать данные с другого домена. Вариантов решения два: либо вы свой сервер настраиваете на cross-domain ajax, либо используете xmltools.ru как сервис, запрашивая трансформ через какой-нибудь php/python/ruby скрипт, отдавая результат трассировки со своего домена.

    Онлайн-версия пока находится в состоянии «альфа», поэтому может быть много багов. И пока бесплатна. Подчёркиваю, пока бесплатна. Дело в том, что такие трассировки генерируют очень большую нагрузку, и если она возрастёт до каких-то критических значений, то придётся прикрыть лавочку и предоставлять услугу на коммерческой основе.

    Метки: , , ,