Возникла одна идея, как можно скрестить клиентское и серверное «вынюхивание» браузера.
Как это делается сейчас? На сервере мы смотрим на заголовок User Agent и определяем, что это за браузер. Но этот заголовок можно легко подменить (причём необязательно это делать в самом браузере, это может сделать прокси-сервер), поэтому такая проверка совсем ненадёжна. На клиентской стороне у нас гораздо больше возможностей: мы можем не только проверить User Agent браузера, но и его возможности (например, поддержку определённых CSS и JS свойств), что даст нам более точные характеристики браузера.
Собственно, сама идея как раз заключается в том, чтобы скрестить эти два способа: определить с помощью CSS и JS характеристики браузера и в нужный момент сообщить их на сервер. Основа алгоритма — Chunked Transfer Encoding, позволяющий передать ответ (например, HTML-документ) не целиком, а кусками. Итак, сам алгоритм:
- Когда пользователь запрашивает HTML-страницу, отдаём ему первый фрагмент (chunk), в котором содержится sniffing-код. После этого соединение удерживается на некоторое время, чтобы отдать второй фрагмент документа.
- Браузер, получив первый фрагмент, тут же его парсит и пытается отобразить.
- В этом первом фрагменте у нас sniffing-код, результатом работы которого является CSS-свойство
background-image
, применяемое к элементу<html>
. - Путь к этой фоновой картинке является уникальным для каждого браузера и так уж получилось, что как только мы применили это свойство элементу, браузер открывает второе соединение и тут же пытается загрузить эту картинку.
- Сервер, получив запрос к нашей «картинке» понимает, каким браузером мы зашли и отдаёт вторую часть ответа в первом соединении.
- Если обращения к «картинке» не было (например, зашли поисковым роботом), то через какой-нибудь небольшой промежуток времени отдаём стандартный ответ.
- После первого «вынюхивания» можно поставить куку, чтобы потом сразу отдавать нужный ответ.
В итоге получаем следующие преимущества:
- Нет никаких редиректов: ответ о типе браузера получаем в одном соединении (полезно для SEO).
- Очень сложно обмануть такой сниффер: можно до посинения менять строку User Agent, но результат останется прежним.
Гугл сможет эффективней банить Оперу. - Более сложные проверки: можно не просто определять название браузера, но и его возможности, благодаря CSS Media Queries.
Для желающих поэкспериментировать я написал демонстрацию этого подхода на node.js. К сожалению, у меня на VPS не хватает памяти, чтобы собрать node.js, поэтому предлагаю читателю самому его установить и проверить у себя. Если кто выложит это в онлайн — буду премного благодарен. Выложили: http://tbms.ru:8126/ (спасибо Николаю Митину).
Полезные уроки
Пока писал эту демонстрацию, вынес для себя много ценной информации:
- Первый чанк должен быть достаточно большим, чтобы все браузеры смогли его применить. Самый большой лимит — у движка Webkit: около 2 КБ. Поэтому приходится «добивать» первый фрагмент пробелами, чтобы всё работало как надо.
- Webkit очень плохо ведёт себя при переопределении CSS-свойств. Например:
html { background-image:url(image1.png); } html { background-image:url(image2.png); }
В этом случае Webkit (и десктопный, и мобильный) загрузит обе картинки, хотя понятно, что нужна только последняя. Это ещё одно подтверждение, что мобильные версии сайта лучше делать отдельным проектом, а не вставлять через CSS Media Queries. Иначе попытки сократить траффик заменой больших фоновых картинок на маленькие приведёт к обратному эффекту. Хотя тут нужно больше исследований, возможно, это издержки работы с локальным сервером.
- Firefox не отработает первый чанк до тех пор, пока не получит элемент
<body>
. Это довольно серьёзный недостаток в том случае, если вы захотите выдавать уникальные стили и скрипты для браузера в секции<head>
. С другой стороны, если послать<script>alert(1)</script>
, то чанк отработает нормально, даже без<body>
. Что наталкивает на мысль о существовании неких триггеров, которые заставят браузер сделать то, что нам нужно, осталось только найти наиболее безопасный.
Повторюсь, что это только идея, а не призыв к действию. Буду рад, если кто-то на её основе сделает что-то крутое и интересное.