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

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

    Как это делается сейчас? На сервере мы смотрим на заголовок 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>. Что наталкивает на мысль о существовании неких триггеров, которые заставят браузер сделать то, что нам нужно, осталось только найти наиболее безопасный.

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

  • 38 комментариев

    1. 24 октября 2010

      3. В этом первом фрагменте у нас sniffing-код, результатом работы которого является CSS-свойство background-image, применяемое к элементу <html>.

      Фон на html может переопределить только другое объявление для html, а фон body будет только на содержимом, т. е. не обязательно на весь экран. Если же фон для html не задан, то, указанный для body, фон распространяется на него по умолчанию в HTML-документах, что может быть довольно удобно (например потому что на <body> можно указать класс, а на <html> — нет). Не сработает ли то же самое при указании background-image для body?

    2. 24 октября 2010

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

      P.S. хотел подписаться OpenID, но твой сайт сказал, что у меня нет Cookie или JS 🙁

    3. 24 октября 2010

      Ooo… с третьего раза OpenID таки сработал.

    4. 24 октября 2010

      Ещё, кстати, недостаток: браузер может просто не послать запрос (выключены картинки, модем сглюкнул или сервер не смог его принять).

    5. 24 октября 2010

      А так — идея мне нравится своей гипернеобычностью.

    6. Сергей Чикуенок
      24 октября 2010

      GreLI, основная идея была в том, чтобы отдать кусок секции head, чтобы потом можно было заполнить её нужными стилями. То есть body, в идеале, в первом чанке быть не должно, но из-за проблем с Firefox я его выдаю.

      Bolk, прокси я не боюсь, просто как пример привёл 🙂 Но в любом случае в демо есть фолбэк, который отдаёт стандартный ответ в случае, если не получил sniff-запрос (у меня это 400 мс). Уверен, что недостатков в этом способе ещё больше. Например, у меня даже на локальном компе sniff-запрос вовремя не посылался из-за лагов в системе: работало какое-то тяжёлое приложение в фоне и за отведённые 400 мс браузер не успел послать запрос. Таймаут, конечно, регулируется на сервере, но всё же.

    7. 24 октября 2010

      Долго получается
      коннект по основному запросу +
      получение хитрого куска с разными путями к background-image ( куска с js ) +
      его обработка на клиенте +
      пауза между основным запросом и запросом к background-image +
      сам дополнительный коннект к background-image +
      обработка на сервере заголовка User Agent для получения background-image

      так и не понял почему нельзя сделать это сразу
      при запросе основного документа ещё на сервере добавить в <html class=»server-verified-browserIE6
      потом загрузить остальную часть документа
      с кодом js, который верифицирует браузер на клиенте и дописывает <html class=»server-verified-browserIE6 client-verified-browserIE6

      что-то очень сложно, зачем двойная верификация браузера?
      Опереться на неё и послать из JS пользователя подальше можно, а робота нет.

    8. 24 октября 2010

      Идея хорошая, но
      1) Не получится gzip для HTML
      2) Смысл в таком сниффинге? Экономия трафика для мобильных устройств? Тогда это немного другая задачка (но тоже очень интересная). Вот jQuery уже собран для настольных и мобильных устройств, это да.
      3) Дополнительный запрос (да еще и в рамках одной «сессии» — cookie придется передавать, иначе как определить, что этот тот самый браузер отправил background-image?) мне не очень нравится.

      В принципе, ничего критичного в том, чтобы вставить CSS/JS-файлы (в данном конкретном случае) в body нет: все равно из браузер получит только после определения, и задержка будет ровно такой же, если бы мы расположили их вызовы перед закрывающим head. Ну да, еще и чуток невалидно.

    9. 24 октября 2010

      Не вижу смысла. Лишний HTTP-запрос, его ожидание первым… К чему такие тормоза?

      Пора вообще про сниффинг забыть. Modernizr же рулит. Уже в большинстве мест использую feature detection с его помощью (:

      А для сборки node.js нужно не памяти много, а процессора. Не просто так говорю, сам собирал.

      P.S. подтверждаю косяки с OpenID

    10. 24 октября 2010

      Простите, если я сильно туплю, но я не понял одной вещи: зачем вообще нужна задержка? Зачем выдавать документ по частям, если используем хаки для определения браузера?

      1. Мы используем feature-detection для того, чтобы выбрать отдельную фоновую картинку для каждого браузера
      2. Картинка загружается
      3. Сервер, выдавая картинку, запоминает браузер.

      Где и зачем здесь нужна задержка? Объясните, пожалуйста. Пока что мне кажется, что это либо какой-то интересный велосипед, либо очень-очень хитрый план, который может понадобиться разве что злоумышленникам.

      А вот неплохая таблица с хаками для определния браузера на чистом CSS: http://studioad.ru/blog/2009-09-30-92

    11. Сергей Чикуенок
      24 октября 2010

      Сергеев Виталий, первое применение, которое я вижу — это отдавать разный контент для десктопа и для мобильных, причём проверять можно именно по текущим возможностям браузера, а не по строке User Agent. Второй вариант — это защита от ботов. В простейших случаях это можно использовать вместо капчти.

      1) Не получится gzip для HTML

      Получится

      Дополнительный запрос (да еще и в рамках одной “сессии” — cookie придется передавать, иначе как определить, что этот тот самый браузер отправил background-image?) мне не очень нравится.

      Посмотри демку, там куки не используются. И запрашивать можно какой-нибудь полезный ресурс, который всё равно понадобится на странице.

      Пора вообще про сниффинг забыть. Modernizr же рулит. Уже в большинстве мест использую feature detection с его помощью

      Сниффинг используется для того, чтобы отдавать клиенту минимально необходимый код для работы (снижая тем самым нагрузку на браузер), а не сваливать всё в кучу. И кстати, Modernizr в этом случае создаст нагрузку на браузер больше, чем сниффинг.

      А для сборки node.js нужно не памяти много, а процессора. Не просто так говорю, сам собирал.

      Я тоже собирал, и компилятор ругался на отсутствие необходимого количества памяти. В любом случае, собрать у себя я его не могу.

      Где и зачем здесь нужна задержка? Объясните, пожалуйста.

      Задержка нужна для того, чтобы если зашли на сайт не браузером (поисковым роботом), который не может отработать CSS, отдать какой-нибудь стандартный ответ

    12. 24 октября 2010

      Лучше пара-тройка килобайт лишнего CSS, чем HTTP-запрос 😉

    13. 25 октября 2010

      2 Dmitry Scriptin
      «определния браузера на чистом CSS»

      в том и дело что не чистом, а с паршивыми хаками, причём уже не работающими в последних обновленных версиях браузеров
      CSS не определения браузера или его возможностей не предназначен,

      Ну нельзя хаки использовать, если конечно вы не уверенны, что за исправления последствий их использования вам лично заплатят потом отдельно:
      для определения возможностей можно обойтись Modernizr’ом (5 кб всё), который если что достаточно легко дробится, можно оставить из него то что хочешь.
      для определения браузера и его версии browserDetect.js — jQuery всё равно ведь используете наверное
      http://www.tvidesign.co.uk/blog/CSS-Browser-detection-using-jQuery-instead-of-hacks.aspx
      <1 кб

    14. Чистяков Денис
      25 октября 2010

      Спасибо, за отличную статью как обычно, на эту тему недавно хорошо написал Илья Кантор в статье: Способы идентификации в интернете.
      Я правильно понял идею — мы один раз отдаем «специальную» страницу, тем самым максимально идентифицируя пользователя и возможности его браузера, а в дальнейшем по полученным идентификаторам пользователя отдаем нужные стили, скрипт и код, да?

    15. GreLI
      25 октября 2010

      Стандартный ответ с задержкой кстати плох тем, что Гугл уже меряет скорость отдачи страницы, и 400 мс для него уже может быть довольно много, что плохо, а на мобильниках задержки запросто могут быть куда больше.

    16. vladfrandom
      25 октября 2010

      Ой как мне этот процесс подбора багов и тонких мест броузеров напоминает ситуацию при выходе новых версий броузеров 2-3 года назад. Сейчас-то все более-менее устаканилось. Я смотрю, у тебя привычное искательство нашло новую лазейку?

    17. 25 октября 2010

      @Виталий Сергеев: я всегда предпочитаю хакам что-нибудь другое, просто привел в пример, раз речь зашла.

      @Сергей Чикуенок: OK, на сайт зашел робот — он не станет скачивать фоновые изображения (но при этом, кстати, может и обрабатывать CSS с целью нахождения «спрятанного» текста, которым балуются некоторые SEOшники).

      Но если на сайт зашли через браузер с отключенными изображениями, то он тоже не станет скачивать фоновые изображения. Таким образом, мы опять не можем отличить браузер от робота. И тут возвращаемся к первому вопросу: зачем нужна задержка?

      Кроме того, если уж апеллировать с нагрузке на браузер, то современные мобильные браузеры поддерживают media queries, которыми проблема и решается.

      Т.е. по обоим пунктам: различение браузеров и роботов и снижение нагрузки на браузер — решение не работает. Если точнее, то работает никак не лучше Modernizr и подобных.

    18. Сергей Чикуенок
      25 октября 2010

      в том и дело что не чистом, а с паршивыми хаками

      В демке нет ни одного хака

      причём уже не работающими в последних обновленных версиях браузеров CSS не определения браузера или его возможностей не предназначен

      Очень хорошо предназначен. Тот же Modernizr часть свойств определяет через именно CSS, но дополнительно использует JS. Никто не мешает такую же практику использовать и для сниффинга

      Стандартный ответ с задержкой кстати плох тем, что Гугл уже меряет скорость отдачи страницы, и 400 мс для него уже может быть довольно много

      Много — это когда страница отдаётся несколько секунд. 400 мс (который, к слову, настраивается) — очень даже неплохое время для генерации страницы

      OK, на сайт зашел робот — он не станет скачивать фоновые изображения (но при этом, кстати, может и обрабатывать CSS с целью нахождения “спрятанного” текста, которым балуются некоторые SEOшники).

      Парсить и применять CSS — разные вещи

      Но если на сайт зашли через браузер с отключенными изображениями, то он тоже не станет скачивать фоновые изображения. Таким образом, мы опять не можем отличить браузер от робота. И тут возвращаемся к первому вопросу: зачем нужна задержка?

      Если не загрузил картинку, то какой контент тогда отдавать? И сколько нужно ждать, чтобы понять, что запрос к серверу не дошел по причине просмотра роботом или из-за лагов машины пользователя?

      Кроме того, если уж апеллировать с нагрузке на браузер, то современные мобильные браузеры поддерживают media queries, которыми проблема и решается.

      Разница в том, что в случае определения браузера на стороне клиента нужно сначала скормить ему тонну CSS и JS, которую нужно скачать и распарсить, а потом уже разгребать, что будем использовать, а что нет. В случае сниффинга отдаётся только тот контент, который нужен этому браузеру. Я выше уже приводил пример, что Webkit скачивает две картинки, даже если через media queries отфильтровали браузер и подсунули что-то упрощённое

    19. 11 ноября 2010

      http://tbms.ru:8126/ — запустил детектилку браузера в онлайне.

    20. Сергей Чикуенок
      11 ноября 2010

      О, спасибо большое!

    21. 11 ноября 2010

      Chrome 7.0.517.44

      You’re watching this page with unknown browser

    22. Сергей Чикуенок
      11 ноября 2010

      Это как раз один из тех недостатков, что я описал: из-за лагов системы не отработал запрос по нужному таймауту. Если обновить страницу, то должно работать

    23. 11 ноября 2010

      Да, действительно, со второго раза определил, но как-то слишком обще

      You’re watching this page with WebKit-compatible browser

      browserDetect.js точнее, а главное проще, т.е. лучше !

    24. Сергей Чикуенок
      11 ноября 2010

      Ещё раз повторюсь: смысл данной идеи в том, чтобы выдать пользователю контент именно под его браузер, а не выдавать всё, а потом разгребать, что показывать, а что — нет. В частности, это очень важно для мобильных версий сайта.

      К тому же, суть демки — не использовать JS (который иногда отключается). С JS’ом этот же сниффинг будет в сто раз проще и точнее.

      И я не предлагаю всем срочно использовать этот метод определения браузера. Это просто идея, которая может быть кому-нибудь пригодится.

    25. Эд
      12 ноября 2010

      Сергей, великолепно. пока очень много «но», но главное фундаментальная затея. Я вот только хочу узнать, откуда информация о том, что прокси режут User-Agent? И почему подмена пользователем User-Agent’а должна нас волновать? Мобильный User-Agent показывает, что он мобильный?

      Я думаю уместно хранить информацию о возможностях User-Agent на сервере и обновлять ее автоматически за счет пользователей вот таким описанный способом. И для сбора данных таймаут на сниф может быть больше, потому это в каком-нибудь скрытом iframe будет выполняться.

      400 мс напрягают одновременно тем, что это много и тем, что этого иногда мало 🙁

    26. Сергей Чикуенок
      13 ноября 2010

      Я вот только хочу узнать, откуда информация о том, что прокси режут User-Agent?

      Общался с коллегами из Google и Yahoo. На то есть разные причины, хотя бы неправильно настроенный прокси. А ещё бывают ситуации, когда в запросе на сервер нет информации о том, что текущий user agent поддерживает gzip. Google в таком случае делает нечто подобное, чтобы узнать, действительно ли поддерживается gzip, экономя тем самым терабайты траффика.

      И почему подмена пользователем User-Agent’а должна нас волновать?

      Как минимум потому, что нужно знать, какой именно браузер у пользователя чтобы отдать соответствующий контент.

      Мобильный User-Agent показывает, что он мобильный?

      Айфон показывать. И суть моего метода как раз более надёжное определение браузера, в том числе мобильного.

      Я думаю уместно хранить информацию о возможностях User-Agent на сервере и обновлять ее автоматически за счет пользователей вот таким описанный способом.

      Так делать нельзя, потому что проверка на user agent считается очень ненадёжной. Недавно, например Google запускал интерактив с шариками на главной и банил Оперу именно по user agent. Как вы думаете, каким образом пользователи Оперы обходили это ограничение и обходят такие же на других сайтах?

      И для сбора данных таймаут на сниф может быть больше, потому это в каком-нибудь скрытом iframe будет выполняться.

      Вы не поняли смысла моей проверки. Суть её не в том, чтобы каким-то образом сказать серверу, каким браузером пользуемся, а чтобы в одном запросе к странице определить браузер и тут же выдать специфичный для него контент. Поэтому никаких iframe’ов тут не может быть в принципе.

    27. Эд
      16 ноября 2010

      Спасибо за ответы, Сергей. Я понял суть проверки, просто свою мысль плохо описал.

      Дело в том, что я смотрю на данную проверку как на решение своей задачи, которая звучит так: «Определить User-Agent пользователя на сервере для отдачи ему контента, польностью удовлетворяющему возможностям его браузера». Т.е. если пользователь режет User-Agent, значит для него будет отдана версия в соответствии с выбранной стратегией Graceful degradation и progressive enhancement.

      Соответственно надежность информации имеет смысл, но всегда можно определить грань и управлять ею. Например из описанных минусов мне пока не нравится информация о неправильно настроенных прокси. Вот тут хотелось бы подробностей. Потому что если мы можем понять, что за браузер к нам едет, но не можем понять поддерживает ли он gzip, то это одно. А если прокси режу наименование и версию (интересно сколько таких в процентах), то это критично. Соответственно интересно как узнать как именно они режут. И использовать описанный в статье прием не для всех пользователей, а для таких, User-Agent которых порезан проксей.

    28. Сергей Чикуенок
      16 ноября 2010

      А если прокси режу наименование и версию (интересно сколько таких в процентах), то это критично. Соответственно интересно как узнать как именно они режут. И использовать описанный в статье прием не для всех пользователей, а для таких, User-Agent которых порезан проксей.

      Они не обязательно обрезают user-agent, они могут заменять его. Например, могут всегда представляться как IE8. Собственно, так некоторые пользователи и делают со своими браузерами (прокси тут не важет, просто приведён как пример).

    29. Эд
      16 ноября 2010

      Если пользователь хочет чтобы я думал, что у него ИЕ8, я не против. Отдам ему контент для ИЕ8. А вот если пользователь ни сном, ни духом, а прокси творит беспредел, то я хочу применить описанный метод. Вопрос как понять, что прокси творит беспредел 🙂

    30. Сергей Чикуенок
      16 ноября 2010

      Сомневаюсь, что это можно понять. Поэтому проверка по User Agent и считается не очень надёжной.

    31. Артем
      17 ноября 2010

      Прокси могут не только user agent резать, но и делать http downgrading.

    32. Артем
      17 ноября 2010

      И есть еще проблема с ssl+ie
      http://www.modssl.org/docs/2.8/ssl_faq.html#io-ie

    33. Никита
      19 ноября 2010

      Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12 (.NET CLR 3.5.30729)

      >>You’re watching this page with unknown browser

    34. 25 ноября 2010

      Кстати, джаваскриптовый подход к сниффингу реальных параметров браузера использует, и весьма успешно, Outlook Web Access (OWA) — всем браузерам, кроме IE, он отдает лайт-версию приложения, даже тем, кто пытается через прокси представить себя как IE.

    35. 25 ноября 2010

      Вдогонку — именно поэтому нет ни одного решения ни для Firefox, ни для Chrome, которое позволило бы отобразить в нем нормальный Outlook Web Access (IE Tab не в счет, там работает реальный IE внутри таба).

    36. 6 декабря 2010

      К слову, в YUI Theater, Dav Glass уже несколько докладов читал про использование YUI3 вместе с node.js. И в Dav Glass — Node.js + YUI 3 последние 5 минут он как раз рассказывает про определение браузера/устройства клиента на сервере (правда на основе UA-String) и на основе полученных данных изменяет страницу.
      По-идее, раз у него в какой-то из периудов загрузки есть «ссылка» на DOM на сервере (если я правильно понял идею), то можно попробовать применить похожую технику, но несколько проще.

    37. 10 января 2011

      ЛУчше смотреть не тип и версию браузера, а его возможности. Хороший фреймворк для определения возможностей браузера has.js. Небольшое описание есть тут: Определение возможностей JavaScript с помощью has.js

    38. 3 ноября 2011

      Excelent info my friend!!, keep it comming!