Wednesday, March 11, 2009

Кеширование и Silverlight

На днях столкнулся на работе с интересной ситуацией, которая в который раз напомнила о том, что .NET веб-программирование - это не только ASP.NET, но еще и куча всего под ним, благодаря чему этот ASP.NET, собственно, и работает. И что знание одного лишь ASP.NET не делает вас настоящим веб-программистом. Нужно знать еще много чего, начиная от HTML/CSS/JS и вплоть до протокола HTTP (хотя бы в какой-то мере). Вот и в нашем случае для решения проблемы понадобились знания HTTP-кеширования...

HTTP-кеширование - это кеширование страниц и других запрашиваемых ресурсов на машине пользователя или промежуточных прокси-серверах. Делается это, естественно, для того чтобы не загружать их постоянно с сервера и таким образом уменьшить трафик и увеличить производительность приложения. Управление кешированием происходит при помощи HTTP-заголовков. Для управления кешированием используются следующие заголовки: Expires, Last-Modified/If-Modified-Since, ETag/If-None-Match, Cache-Control, Pragma, X-Cache (который не является стандартным HTTP-заголовком, но тем не менее используется веб-серверами и прокси-серверами вроде squid) и т.д.

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

Итак, есть Silverlight-приложение, которое на веб-сервере представлено XAP-файлом. Наверно, вы в курсе, что во избежание постоянных загрузок этих иногда очень даже немаленьких файлов с сервера, XAP-файлы по умолчанию кешируются в браузере. Ну, как обычные ZIP-файлы, например. Так вот, заливаем мы обновленную версию XAP-файла на удаленный сервер, пробуем запустить с одного компьютера - приложение обновлено, с другого - нет, показывается старая версия (тут необходимо отметить, что компьютеры находятся в разных локальных сетях). Ну, закешировал браузер, с кем не бывает. Очищаем кеш в FF - не работает, по-прежнему старая версия. Делаем Ctrl-F5 в IE - то же самое. Никаких изменений. Чешем репу, пробуем еще раз. И еще раз. С разными вариациями. Лезем в гугл - гугл что-то говорит по поводу кеширования SL-приложений, что нужно настроить в IIS опцию не кешировать приложение (свойства XAP файла в IIS -> HTTP Headers таб -> "Enable Content Expiration" -> "Expire Immediately" с вариациями в зависимости от версии IIS). Пробуем - не получается. Похоже, придется все-таки включать голову. Берем Fiddler, запускаем, смотрим запросы на XAP-файл. Так и есть, берет из кеша браузера. Делаем Ctrl-F5 - запрос уходит, файл получен заново... но он старый! Быстро вспоминаем, где еще могут быть закешированы файлы - на прокси-серверах. Ага! Быстрая проверка с удалением файла на сервере, который тем не менее, отлично заново откуда-то загружается подтверждает теорию. HTTP-заголовки тоже ее подтвержают:

Date: Thu, 05 Mar 2009 09:08:19 GMT
Age: 454965

Значит, файл действительно закеширован на прокси-сервере. Заголовок X-Cache (если вам повезло и он есть) позволяет даже узнать на каком:

X-Cache: HIT from <domain.com>

Здесь <domain.com> - адрес нашего родного корпоративного проксика :) Дальше дело за малым - перенастроить squid, чтобы он не кешировал запросы с корпоративного staging-сервера - и готово. Или еще не готово?

Конечно, не готово. А если у клиента или его end-user'ов прокси-сервер тоже настроен таким же образом? С одной стороны, это не так страшно, если вы не собираетесь часто обновлять приложение, но с другой стороны наш случай показал, что простое обновление файла НЕ СБРАСЫВАЕТ кеш прокси-сервера! То есть браузер получает файл прямо из прокси, а тот даже думать не желает лезть за обновлением на веб-сервер. Почему? Так настроен Cache-Control, который, во-первых, public (то есть доступен для кеширования не только в браузере, но и на прокси-серверах), а во-вторых, не содержит опции по проверке обновления файла по умолчанию. Из этого следует, что в случае, если между приложением и клиентом находится кеширующий прокси, и вы обновили приложение, то пользователи не увидят его до тех пор, пока кеш прокси-сервера не сбросится. А это не всегда приемлемо.

Как же решить проблему раз и навсегда? Можно рубануть с плеча и поступить так, как советуют люди, установив опцию "Expire Immediately" для вашего XAP-файла в IIS. Но если вы сделаете это, то у вас КАЖДЫЙ запрос пользователя будет заканчивать перезагрузкой файла с сервера. Что при размере последнего больше 500 Кб-1 Мб равносильно самоубийству в особо извращенной форме для какого-нибудь вебдванольного сайта с большим количеством посещений. Есть ли способ гуманнее? Да, есть, хотя я еще и не пробовал его, поэтому действуйте на свой страх и риск. Поскольку IIS не дает нам сильно разбежаться в плане установки своих кастомных заголовков для файлов, то здесь в одном из ответов рассказывается, как сделать HTTP-хендлер, который будет устанавливать необходимые заголовки (как минимум Cache-Control: private для production и no-cache или даже no-store для девелоперских машин). К слову, это же поможет вам и в девелопменте, т.к. каждый раз давить Ctrl-F5 или очищать кеш в FF - это тоже не выход, на мой взгляд. Описывать реализацию не буду - думаю, сами справитесь да и время уже позднее :)

Удачи вам в Silverlight-девелопменте!

4 comments:

  1. Проблема закешированных ресурсов решаеться значительно проще. Достаточно в название файла включать версию файла. Мы так и с флешом делаем и с джаваскриптом.

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

    ReplyDelete
  3. Мне кажется, лучшее решение здесь: http://codeblog.larsholm.net/2010/02/avoid-incorrect-caching-of-silverlight-xap-file/

    ReplyDelete
  4. Да, тоже красивое решение. Спасибо

    ReplyDelete