UPD0 (2016-07-19 23-31): судя по всему, первая половина моей статьи — успешно изобретённый велосипед. Спасибо хабравчанам за ссылку на спецификацию
Статья ценна не более, чем вольное описание уже придуманной технологии.
--# Предыстория
Июльский субботний вечер подходил к концу. Нарубив дров на шашлык, я повесил USB-модем на багету, скомандовал sudo wvdial
, развернул браузер и обновил вкладку с открытым гитхабом. Вернее, попытался обновить. Скорость не радовала, и в итоге страница-то обновилась, но явно не хватало какого-то из стилевых файлов; и дело было не в блокировке, поскольку аналогичные проблемы я наблюдал и с другими сайтами, и зачастую они решались просто многократным обновлением страницы. Во всём был виноват перегруз 3G-сети.
Стоп! А как же кэш?
Недолгое гугление привело на официальный гугловский мануал. Целиком пересказывать его не буду; скорее всего, дело было в том, что браузер прилежно ждал, когда сервер передаст ETags, а ответ сервера затерялся в переполненных триджунглях.
Через пару дней, возвращаясь душным днём из кафе, я придумал рацпредложение, которое решает эту (и несколько других проблем), которое и излагаю в данной статье.
Суть предложения
Добавить ко всем тэгам для подключения подчинённой статики (стилей, скриптов, изображений) атрибут checksum
, который бы хранил хэш (например, SHA-1, как в git) требуемого файла:
<link href="//habracdn.net/habr/styles/1468852450/_build/topic_form.css" rel="stylesheet" media="all" checksum="ef4547a3d5731e77f5a1a19e0a6d89915a56cd3a"/>
Найдя в теле веб-страницы подобный тэг, браузер смотрит, есть ли объект с таким хэшем в кэше, и если есть, то не отправлять никаких запросов вообще: и так понятно, что файл — ровно тот, который требуется. Файлы в кэше браузера лучше сразу хранить с именами, соответствующими их хэшам, как это делает тот же git.
Обратная совместимость предлагаемого решения очевидна.
Какие проблемы это решает?
Пресловутая угадайка: актуален ли файл в кэше?
- Больше не нужно отправлять запрос и сличать полученные ETags.
- Даже если файл в кэше вроде как устарел, но хэш совпадает — его можно смело использовать.
- Чистка кэша как средство решения проблем частично теряет актуальность.
Дилемма: jQuery со своего домена или с CDN?
Владельцам малых сайтов часто приходится выбирать: либо подключать jQuery и/или подобные ей библиотеки с CDN (гугловского, например), или со своего домена.
В первом случае уменьшается время загрузки сайта (в том числе первичной, т.е. при первом заходе посетителя на сайт) за счёт того, что файл с серверов Гугла с большой долей вероятности уже есть в кэше браузера. Но, например, разработчики WordPress придерживаются второго варианта, ставя во главу угла автономность. И в условиях, когда CDN падают, блокируются и т.д., их можно понять.
Теперь от такой проблемы можно будет избавиться навсегда: не всё ли равно, откуда получен файл, если его содержимое — это ровно то, что нужно html-странице, и она это удостоверяет? Можно смело указывать свой домен, и если библиотека есть в кэше (неважно, загруженная с этого сайта, другого "малого" сайта или из какого-нибудь CDN) — она подхватится.
Смешанный HTTPS/HTTP-контент
Одна из причин запрета загрузки HTTP-ресурсов на HTTPS-страницах — возможность подмены HTTP-контента. Теперь это больше не преграда: браузер может получить требуемый контент и сверить его хэш с хэшем, переданным по HTTP. Отмена запрета на смешанный контент (при наличии и совпадении хэша) позволит ускорить распространение HTTPS.
Косвенное определение истории по времени загрузки статики
Известно, что владелец некоторого сайта evilsite.org
может (с некоторой долей вероятности) определить, был ли посетитель на другом сайте goodsite.org
, запросив, например, изображение goodsite.org/favicon.ico
. Если время загрузки иконки ничтожно мало — то она в кэше, следовательно, посетитель был на сайте goodsite.org
. Теперь эта атака усложнится: околонулевое время отклика будет лишь обозначать, что посетитель был на сайте с таким же фавиконом. Это, конечно, не решает проблему целиком, но всё же несколько усложняет жизнь определяющему.
На что это не влияет?
- На html-страницы
- На изображения, стили и скрипты, открываемые по непосредственной ссылке, а не служащие вспомогательными элементами страницы.
- На изображения, стили и скрипты, которые не предполагаются неизменными, например, когда подключается самая новая версия некоторой библиотеки с CDN этой библиотеки.
Идеология
Как обычно (математик я, что уж тут поделать) сформулируем аксиомы, которые вкладываются в предложение:
- Все передаваемые файлы делятся на главные (в основном html-страницы) и подчинённые (скрипты, изображения, стили и т.д.).
В идеологии, заложенной в стандарты HTTP-кэширования, все файлы равноправны. Это, конечно, толерантно, но не отвечает современным реалиям. - Неважно, откуда получен подчинённый файл. Важно, что его содержимое удовлетворяет нужды главного.
В существующей идеологии даже сама аббревиатура URI — Uniform Resource Identifier — предполагает, что идентификатором ресурса является его адрес в сети. Но, увы, для подчинённых файлов это несколько не соответствует действительности.
Перспективы
Обещанный няш-меш
Зная хэш требуемого вспомогательно файла, можно почти смело запрашивать его у кого угодно; основная опасность: если опрашиваемый узел действительно имеет требуемый файл, то он знает его содержимое и, скорее всего, как минимум один URI-адрес, по которому требуемый файл может (или мог) быть получен. Имеем два варианта использования предлагаемой технологии с учётом этой угрозы с целью плавного подхода к няш-меш сети:
Доверенные устройства
Например, в офисе работают программисты, ЭВМ которых объединены в локальную сеть. Программист Вася приходит рано утром, открывает гитхаб и получает в кэш стили от нового дизайна, который выкатили ночью (у нас — ночь, там — день). Когда в офис приходит программист Петя и тоже загружает html-код гитхабовской странички, его ЭВМ спрашивает у всех ЭВМ в сети: "А нет ли у вас файла с таким-то хэшем?" "Лови!" — отвечает Васина ЭВМ, экономя тем самым трафик.
Потом наступает перерыв, Вася и Петя лезут смотреть котиков и пересылают фотографии друг другу. Но каждый котик скачивается через канал офиса только один раз...
Анонимный разделяемый кэш
Аня едет в трамвае с работы и читает новости… например, на Яндекс-Новостях. Встретив очередной тэг <img>
, Анин телефон со случайного MAC-адреса спрашивает всех, кого видит: "Ребят, а ни у кого нет файла с таким-то хэшем?". Если ответ получен в разумное время — профит, Аня сэкономила недешёвый мобильный трафик. Важно почаще менять MAC-адрес на случайный да не "орать", когда в поле видимости слишком мало узлов и спрашивающего можно идентифицировать визуально.
Разумность времени ответа определяется исходя из стоимости трафика.
Дальнейший переход к няш-мешу
Фотография в соцсети может быть представлена как блоб, содержаший хэш и адрес собственно изображения (возможно, в нескольких различных размерах), а также список комментариев и лайков. Этот блоб тоже можно рассматривать как вспомогательный файл, кэшировать и передавать друг другу.
Более того, альбом фотографий тоже легко превращается в блоб: список хэшей изображений + список хэшей блобов-фотографий (первое нужно, чтобы при добавлении лайка/комментария показывать фотографии сразу, а метаинформацию — по мере её получения).
Останется только реализовать электронную подпись и поля вида "замещает блоб такой-то" — и готова няш-меш-социалочка.
Компактизация хэша
В идеале при записи хэша следует использовать не шестнадцатеричную систему счисления, а систему с бОльшим основанием (раз уж мы взялись экономить трафик). Ещё одна идея — атрибут magnet
, содержащий magnet-ссылку. Дёшево, сердито, стандартизировано и позволяет указывать также несколько классических адресов источников, что бывает немаловажно в случае ковровых блокировок и в случаях, когда браузеру известно, что трафик к различным серверам тарифицируется по-разному.
Поведение при несовпадении
Возможна ситуация, когда хэш полученного файла не совпал с требуемым. В таком случае разумно бы было предусмотреть мета-тэги, указывающие браузеру, следует ли такой файл использовать (по умолчанию — нет) и следует ли сообщить об инциденте серверу (по умолчанию — нет).
Файлы-альтернативы
В некоторых случаях можно использовать любой из нескольких файлов с разными хэшами. Например, на сайте используется минифицированная jQuery, но если в кэше браузера есть неминифицированная — что мешает использовать её?
Превентивное кэширование
Многие устройства работают в двух режимах: когда интернет условно-безлимитен (например, мобильный телефон в вай-фай сети) и когда интернет ограничен (лимит по трафику или узкий канал). Браузер или расширение к нему может, пользуясь безлимитным подключением, заранее скачивать популярные библиотеки (наподобие jQuery и плагинов к ней), также по мере необходимости их обновлять. Это ли не мечта многих, чтобы jQuery была включена в браузер?
Заключение
Выдвигаемое рацпредложение актуально, так как борьба за оптимизацию загрузки сайтов идёт полным ходом. Более всего выиграют малые и средние сайты за счёт разделяемых библиотек (и, может быть, некоторых часто используемых изображений) в кэше. Уменьшится потребление трафика мобильными устройствами, что важно с учётом ограниченной пропускной способности каналов сотового интернета. Крупные сайты также могут уменьшить нагрузку на свои серверы в случае, если будут внедрены mesh-технологии.
Таким образом, поддержка предлагаемой технологии выгодна и вебмастерам, чьи сайты будут грузиться быстрее, и производителям браузеров, которые тоже будут быстрее отображать страницы, и провайдерам, у которых уменьшится потребление полосы (пусть и не столь значительно, но от провайдеров активных действий и не требуется).
P.S.
Мне было бы очень приятно услышать мнение Mithgol, Shpankov и BarakAdama.
P.P.S.
Хабр всезнающий, в какое спортлото отправлять рацпредложение?
Комментарии (19)
MTonly
19.07.2016 21:02+4Кстати, аналогичный атрибут, хранящий хэш скрипта или файла таблицы стилей, уже предусмотрен и называется integrity, правда, предназначен он сейчас исключительно для проверки целостности файла с целью предотвращения подмены файла по пути от сервера к пользователю.
thousandsofthem
19.07.2016 22:07+2Насколько мне известно, проблема в реализации указанного в статье только одна — cache poisoning.
Именно из-за этого хэш использется только для проверки целостности и никак иначе
оф дока: www.w3.org/TR/2014/WD-SRI-20140318/#risks-1
nazarpc
19.07.2016 21:261) Для нормальной работы кэша достаточно указать правильные заголовки — браузер не будет ничего грузить
2) Более сложные сценарии (в том числе работа offline) уже успешно решены с помощью WebSockets
bakhirev
19.07.2016 22:05+1Это плохо, т.к.:
- Обновил один файл стилей
- Пересобрал 10 страниц сайта, т.к. в теле каждой изменилось значение одного хеша.
Итого: вместо 1 файла, браузер должен будет обновить 11.
Имхо: если интернет настолько медленный, что даже ETags не затянуть, то можно считать, что его нет вообще.
NickKolok
19.07.2016 23:17ETags отваливался не у всех файлов, а с вероятностью примерно процентов 10.
Ivan_83
19.07.2016 22:05Всё несколько проще.
1. Хэш и контрольная сумма (checksum) сильно разные вещи. Я бы взял какойнить blake или sha512.
2. Есть какой то E-tag в хттп заголовках, вроде можно туда писать хэш. Заодно можно будет с помощью HEAD его получать.
3. На л2 я бы не лез, лучше подписаться на определённый адрес мультикаст рассылки в в4 и в6, и слать туда запросы юдп пакетами. Ответы так же директами, в ответе номер порта куда по хттп приходить за файлом.
Собрать такую штуку для локалки не сложно, но по сути это скорее распределённый кеш а не меш.
В вафлю так просто что попало слать не получится.
Вопрос как противостоять дос и не становится репитером-усилителем остаётся открытым.
nikitasius
19.07.2016 22:06jQuery со своего домена или с CDN?
Замечательный CDN от Cloudflare: https://cdnjs.com
Если же какой-то модифицированный jquery, то со своего сервера через тот же CF (на бесплатном тарифном плане).
sumanai
19.07.2016 22:07+2> Больше не нужно отправлять запрос и сличать полученные ETags.
А ведь достаточно просто не прописывать ETags, используя вместо него старый добрый Expires, и не нажимать F5, а просто щёлкать по ссылкам.
И тогда браузер вообще не будет отправлять запрос в сеть, а будет использовать кеш, если дата в Expires не истекла.
Sobic
20.07.2016 09:15+1или еще вот ipfs.io
Mithgol
21.07.2016 00:01И так как NickKolok желал знать моё мнение, то вот оно: я также считаю, что для распределённого хранения данных с адресацией по контенту более всего подходит IPFS.
Рекомендую к прочтению блогозапись «Почему Интернету нужен IPFS, пока ещё не поздно» и её обсуждение на Хабрахабре.
MadridianFox
«Аня едет в трамвае с работы и читает новости…»
А рядом сидит сурового вида парень в толстовке с капюшоном, который ворует чужие сессии через раздачу заражённого jquery с подогнанным хешем.
MTonly
Как вариант, локальные версии файлов популярных библиотек мог бы периодически загружать сам браузер из гарантированно надёжных источников.
MadridianFox
Тогда теряется вся няш мешность.
NickKolok
Взять алгоритм, коллизии в котором неизвестны и их вероятностью можно пренебречь — и все дела.
tkf
Указывать также и размер файла, или же указывать связку из двух хэшей. Или и то и другое вместе. Задача найти коллизию в таком случае становится более интересной.