Вебные хитрости: Принудительный рефреш статики

    Дано

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

    Ситуация

    Вы только что радикально переработали шаблон страницы (выровняли отбивки, поменяли цвета, прописали фоновые картинки). Получилось реально круто! Пора закачивать изменения на сервер. Вы запускаете FTP-клиента, перетаскиваете gif-ки и css-ки с левой панели на правую и, весь такой довольный, открываете вебсайт в своем любимом браузере…


    «МАТЬ! Мать! мать!» — по привычке гулко отозвалось эхо.

    Что за нах? Куда все съехало? Почему блоки налазят один на другой? Откуда эта синяя полоска? Что за тупорылый шрифт?

    В голове взрывается мысль: «Мля, вот это вот угробище сейчас видит весь мир! Ладно, не весь, но по крайней мере та его часть, что ходит на мой сайт! Что делать, мать вашу, что делать?!».

    Спокуха! Без паники. Ничего страшного не случилось. Все в порядке.

    Достаньте из загашника любой залежалый браузер, которым вы не пользовались сто лет. Что там у вас, Нетшкаф? Симанки? Восьмая Опера? Неважно, пойдет. Открывайте вебсайт. Ну что, отлегло? Все выглядит, как и должно быть? Правильно. Именно так сейчас видят ваш сайт все новые посетители. А бардак в вашем любимом браузере — из-за того, что старое содержимое файла CSS сохранилось в браузерном кэше, и все те стили, над которыми вы корпели последние два дня, еще просто не добрались до вашего компа. Кстати, поправить это дело лечге легкого — достаточно просто нажать Ctrl + F5.

    Другое дело, что не все посетители такие продвинутые, и есть вероятность, что кое-кто из них будет вынужден лицезреть съехавшую картинку в течение длительного времени. Прямо хоть подсказку им пиши: «Граждане, у кого сайт выглядит криво, жмите Ctrl + F5».

    А может, есть способ получше? Нельзя ли как-то заставить браузер принудительно обновить кэш?

    Забегая вперед, могу вас обрадовать: есть такой способ! Причем способ до безобразия простой, на сто процентов валидный и абсолютно безотказный. Впрочем, обо всем по порядку. Серьезно, так будет полезнее.

    Анализ

    Итак, браузер и его кэш. От чего зависит, пойдет ли браузер за новым содержимым или достанет старую копию? От многих факторов, на самом деле. От типа браузера. От версии. От платформы. От пользовательских настроек. От того, с какими HTTP заголовками с сервера отдается статика. От наличия проксей на пути между вебсервером и вашим компом. Короче, много от чего. Есть ли над всем этим хозяйством какой-то контроль? Можно ли дать им всем команду забить на кэш и дружно ломануться за новым содержимым?

    Короткий ответ — нет. Но есть варианты. Которые мы сейчас и рассмотрим.

    1. Заголовок HTTP Cache-Control

    Теоретически можно было бы сопровождать отдаваемые с сервера статические ресурсы таким заголовком:

    Cache-Control: no-cache

    Это бы гарантировало использование клиентом актуального содержимого серверных файлов. К сожалению, этот способ обладает существенным недостатком: создает чрезмерную (и никому не нужную) нагрузку на сеть и другие ресурсы: ведь теперь при каждом запросе страницы браузеру придется заново подгружать сопутствующую статику: файлы стилей, скрипты JavaScript и картинки графики. Кроме того, не все умеют и не всегда имеют возможность управлять заголовками HTTP.

    Можно указать и другое значение заголовка, например:

    Cache-Control: max-age=86400

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

    2. Заголовок HTTP Last-Modified

    В принципе большинство серверов автоматически прописывают этот заголовок при отдаче статических ресурсов. Это позволяет браузеру закачивать новое содержимое только в том случае, если файл реально был изменен. Для этого вместе с запросом GET браузер шлет заголовок:

    If-Modified-Since: Tue, 23 Jun 2009 19:12:47 GMT

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

    Так как же все-таки добиться от него требуемого поведения? Для этого существует радикальное средство, а именно:

    3. Переименование файла

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

    Ура! Проблема решена!

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

    Но погодите, а что если… Что если менять не файл, а… Ну конечно! Эврика!

    4. Переименование URL

    Вспомните, что URL может содержать, помимо имени файла, еще кучу разных составляющих. Например, Query String — та часть, что идет после знака вопроса. Думали, это годится только для форумных движков: /index.php?showthread=1234? А вот и не угадали. Кто захочет, тот и воспользуется. Например, мы для нашего файла стилей. Как насчет прописать такую строку:

    <link rel="stylesheet" href="/style.css?ver=123" type="text/css" />

    Красиво? А то! Смотрим, что получается. Браузер закачивает HTML-код страницы. Видит указатель на таблицу стилей. Смотрит на URL: /style.css?ver=123. Проверяет в кэше и находит старую версию: /style.css?ver=122. Для браузера это два совершенно разных адреса, поэтому он не колеблясь выдает запрос GET на получение нового файла CSS.

    Теперь в дело вступает веб-сервер. Он анализирует серверный адрес ресурса и определяет, что запрашивают статический файл style.css, а раз так, то хвостовая часть URL'а ему по барабану. Поэтому он берет и с чистой совестью отдает клиенту содержимое указанного файла — того самого, который вы только что изменили.

    Телемаркет!

    На самом деле данный подход можно разнообразить множеством модификаций — например, использовать еще более простой адрес вида /style.css?123, или задействовать серверный код, который будет автоматически преобразовывать дату изменения файла в строку и подставлять ее в виде хвостовика. Но это уже все детали. Главное, вы теперь знаете, как обеспечить адекватное отображение страницы после изменения стилей.

    Удачи в сайтостроительстве!

    Update

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

    1. Некоторые браузеры (в первую очередь IE 6+) не кэшируют страницы, в УРЛе которых присутствуют переменные.

    Неправда. Вполне себе кэшируют. Были отсмотрены километры логов на различных сайтах с разными конфигурациями, и я могу ответственно заявить: такого, чтобы какой-то браузер принципиально не кэшировал страницы с хвостовиком в адресе, замечено не было. Дальнейшее изучение вопроса в инете показало, что для уверенности желательно отдавать статику с явно прописанными директивами кэширования Cache-Control, и тогда вопрос практически полностью снимается.

    2. Лучше модифицировать не переменную, а постоянную часть адреса URL, а для перенаправления на постоянный файл использовать mod_rewrite.

    Что можно сказать по этому поводу: абсолютно рабочее решение, которое снимает все вопросы с кэшированием на клиенте. Всячески рекомендуется всем, у кого есть доступ к конфигурации mod-rewrite. Однако в ситуациях, когда доступ к файлам вебсайта это все, что у вас есть в наличии, вас вполне устроит способ с переменными в URL, изложенный в заметке.

    3. Делать все переименования в рамках процесса автоматической сборки и деплоймента проекта.

    Товарищи! Эта заметка меньше всего адресована людям, участвующим в потоковой разработке вебного софта. Если у вас все настолько пучком, что сайт у вас деплоится по нажатию кнопки, такие вещи как переименование статики вам сто пудов должны быть известны.

    Примечание: «Вебные хитрости» — это мини-серия, в которой я делюсь крупицами вебмастерского опыта, приобретенного за более чем пятилетнюю историю сопровождения и развития авторского проекта Английский без дураков.
    Share post

    Comments 31

      0
      Последний вариант, кстати, использует ВКонтакте. А для того, чтобы страница пользователя оставалась всегда актуальной, вне зависимости от настроек кеша, там к URL добавляется рандомное 4-х значное число.
        –1
        Не жалеют они трафик пользователей))
        Вместо того чтобы прописать для статики уникальные урлы...=)
          0
          > А для того, чтобы страница пользователя оставалась всегда актуальной, вне зависимости
          > от настроек кеша, там к URL добавляется рандомное 4-х значное число.

          А они что, не слышали про Cache-Control? С рандомными числами в качестве «уникализатора» не все здорово, на самом деле.

            0
            А чего там не здорово?
              0
              Практически все браузеры (ручаюсь за FF2-3 и IE7-8) отлично все кэшируют, если им правильно сказать как они должны это делать. Например,

              Cache-Control: «public, must-revalidate» (говорит о том, что можно кэшировать, но при этом надо валидировать)
              Expires: -1 (говорит о том что ресурс устарел и его надо проверить, но не всем браузерам это обязательно говорить)
              Last-Modified: дата (на основании этого сервер шлет либо 200 OK, либо 304 Not Modified)

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

              Рандомные числа — это что-то вроде «хака». Но честно говоря, разобраться в RFC 2616 сложнее, чем быстренько все имплементировать с помощью этого простого «хака».
            0
            полезно.
              +2
              стоит ознакомиться с книжкой
              speedupyourwebsite.ru/books/speed-up-your-website/
              там все это расписано вдоль и поперек, и не один раз
                0
                Спасибо, хорошая книжка. Однако наличие хорошей книжки по предмету на гарантирует, что все ее обязательно прочтут. В комментах к статье по меньшей мере двое отметили, что возьмут описанный здесь способ на вооружение.
                0
                Я до этого момента был как раз одним из тех, кто переименовывает CSS-файлы… Теперь наверное совесть не позволит не юзать это красивенное решение !:)
                  0
                  красивенное решение отрубает кеширование у пользователей ie (файлы с "?" в урле запрашиваются с сервера каждый раз), так что осторожно.
                    0
                    а вот и нет, Firefox кеширует. Интересно, конечно, почему
                      0
                      причем тут Firefox?
                        0
                        а, сорри, не заметил ie.
                  +1
                  Насколько я помню, ie не кеширует файлы с Query String
                    +3
                    Кстати, есть самый простой и действенный способ — в Query String делать timestamp от времени последнего изменения файла.
                      0
                      Способ, действительно, очень хороший, поскольку избавляет от необходимости править шаблон страницы, и, кстати, упоминается в конце статьи, но не надо забывать, что не у всех есть возможность или умения, чтобы закодировать эту логику на серверной стороне.
                      +1
                      Можно заюзать rewrite УРЛов и ставить УРЛы такие — /style.css/123
                      –1
                      если изменения происходят «время от времени» (скажем, раз в месяц), то я пользуюсь крайне простым способом:
                      меняю вручную название css файла.
                        +7
                        материал хорош, но сильно много бесполезного текста
                          0
                          Спасибо за положительную оценку, а что касается бесполезного текста, то ведь не все в равной степени владеют материалом: кому-то именно «бесполезный» текст поможет понять логику изложения.
                            0
                            Просто часто материалы просматриваются по диагонали и лишние лирические отступления просто мешают оперативному восприятию информации.
                            Спасибо за статью и поднятую тему.
                            –2
                            зато читать приятно — люблю такой стиль изложения
                            0
                            таким же способом engine.js?build=1 можно «перегружать» js файлы (они тоже кешируются)
                              +2
                              > Вы запускаете FTP-клиента, перетаскиваете gif-ки и css-ки с левой панели на правую и, весь такой довольный, открываете вебсайт в своем любимом браузере…

                              бардак начался уже здесь, нужно было воспользоваться svn или другим механизмом, который поддерживает целостность версии.
                              • UFO just landed and posted this here
                                0
                                может я ошибаюсь, но браузеры (все) вообще не должны кешировать что-либо с переменными в get запросе, независимо от заголовков сервера.

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

                                а для обновления статики проще всего сделать реврайт урлов типа /(\w+)_\d+\.(\w+) -> /$1.$2
                                тем самым подсовывая пользователям, когда необходимо, новое название файла (типа style_1.css, style_2.css для файла style.css), а сам файл переименовывать не надо.
                                  0
                                  А Ruby on Rails самостоятельно приписывают к URL статических ресурсов строчку ?(дата-изменения) :)
                                    –2
                                    Отличная статья спасибо.
                                      0
                                      Отсекать у людей с IE возможность закешировать данные не очень хорошо.
                                      Лучше файл переименовывать. А еще лучше, чтобы он автоматически переименовывался. А еще лучше — организовать у себя нормальную системы сборки файлов, чтобы все нужные файлы собирались в 1, сжимались и переименовывались. В django для этого есть django-compress, например.
                                        0
                                        Самый лучший способ, это использовать рандомное число не в query string а в имени файла, и использовать mod_rewrite.
                                        В частности можно:
                                        ./cache/1234567/style.css — старый
                                        ./cache/1111111/style.css — новый
                                        а реально эти файлы должны ссылаться на один. Как говорил выше, добиться этого можно с помощью mod_rewrite
                                          0
                                          Для продакшен версии сайта, используем номер ревизии в SVN, а для дев версии — timestamp.

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