xmlhack.ru logo
>> статьи на xmlhack.ru

Правильное HTTP-кэширование: httplib2

Автор: Джо Грегорио
Перевод: А.Скробов
Опубликовано на XML.com (01.02.2006, англ.): http://www.xml.com/pub/a/2006/02/01/doing-http-caching-right-introducing-httplib2.html
Опубликовано на xmlhack.ru (12.02.2006, рус.): http://xmlhack.ru/texts/06/doing-http-caching-right/doing-http-caching-right.html
В закладки:   Del.icio.us   reddit

Вам непременно нужно понимать, как работает HTTP-кэширование. Честное слово, нужно. Я много раз упоминал, что выбор HTTP-методов крайне важен при проектировании веб-сервиса, — в частности, выбор метода GET позволяет получить выигрыш от использования HTTP-кэширования. Так вот: для того, чтобы получить от использования метода GET все те преимущества, которые он способен обеспечить, нужно понимать, как HTTP-кэширование работает, и как можно, эффективно его задействовав, увеличить производительность вашего сервиса.

В этой статье я не буду объяснять, как настроить кэширование в том веб-сервере, который вы используете, и не буду описывать различные виды кэширования. Если вам всё это интересно, то рекомендую ознакомиться с замечательным учебником по HTTP-кэшированию от Марка Ноттингема.

Цели

Самое первое, что вам нужно понимать — какие цели преследуются моделью кэширования, используемой в HTTP. Одна из таких целей — это позволить и клиенту, и серверу задавать условия, в которых передаваемый документ может браться из кэша. Легко видеть, что одно это уже привносит в модель кэширования определённую сложность.

Основу модели кэширования, используемой в HTTP, образуют валидаторы — части запроса, используемые клиентом для того, чтобы убедиться, что кэшированный документ всё ещё действителен (т.е. не устарел). Использование валидаторов позволяет клиенту или промежуточному серверу проверять текущее состояние документа, не передавая на сервер его кэшированную копию целиком. Сервер же, в свою очередь, передаёт в ответе документ только в том случае, если полученный им валидатор свидетельствует о наличии в кэше клиента устаревшей (недействительной) копии.

Валидаторы

Один из валидаторов, используемых в HTTP — это ETag. ETag представляет собой хеш («отпечаток») байтов документа: если в документе изменится хоть один байт, то изменится и ETag.

Перед тем, как использовать этот валидатор, нужно хоть раз запросить документ методом GET: если сервер поддерживает эту возможность, то заголовок ETag в его ответе будет содержать хеш передаваемой версии документа. Клиент в этом случае сохраняет в кэше вместе с самим документом его ETag, и в дальнейших запросах того же документа использует сохранённый ETag в качестве валидатора.

Например, пусть я запросил документ с сервера example.org, и получил такой ответ:

HTTP/1.1 200 OK
Date: Fri, 30 Dec 2005 17:30:56 GMT
Server: Apache
ETag: "11c415a-8206-243aea40"
Accept-Ranges: bytes
Content-Length: 33286
Vary: Accept-Encoding,User-Agent
Cache-Control: max-age=7200
Expires: Fri, 30 Dec 2005 19:30:56 GMT
Content-Type: image/png 

-- binary data --

Тогда в следующий раз, когда я запрашиваю этот же документ методом GET, я смогу использовать валидатор. Обратите внимание: сохранённое значение ETag передаётся в заголовке If-None-Match.

GET / HTTP/1.1
Host: example.org
If-None-Match: "11c415a-8206-243aea40"

Если за время между запросами документ не изменялся, то сервер вернёт код 304 Not Modified.

HTTP/1.1 304 Not Modified 
Date: Fri, 30 Dec 2005 17:32:47 GMT

Если же документ менялся, то сервер вернёт код 200 и передаст в ответе новую версию документа, а также значение ETag этой новой версии.

HTTP/1.1 200 OK
Date: Fri, 30 Dec 2005 17:32:47 GMT
Server: Apache
ETag: "0192384-9023-1a929893"
Accept-Ranges: bytes
Content-Length: 33286
Vary: Accept-Encoding,User-Agent
Cache-Control: max-age=7200
Expires: Fri, 30 Dec 2005 19:30:56 GMT
Content-Type: image/png 

-- binary data --

Cache-Control

Валидаторы используются для проверки того, не изменился ли документ; для того, чтобы управлять сохранением его копии в кэше, используется заголовок Cache-Control. Самая главная из директив управления кэшем — это max-age: она указывает, что сохранённая в кэше копия документа устаревает через max-age секунд. Эта директива может использоваться как в запросе, так и в ответе, — так что и клиент, и сервер могут решать, сколько времени будут действительны передаваемые ими документы. Если на сервере есть достаточно новый кэшированный ответ, то его можно вернуть прямо из кэша; иначе же происходит описанная выше процедура валидации.

Рассмотрим ещё раз полученный нами от сервера ответ. В нём заголовок Cache-Control задаёт max-age=7200, т.е. кэшированная копия документа перестаёт быть действительной через 2 часа.

HTTP/1.1 200 OK
Date: Fri, 30 Dec 2005 17:32:47 GMT
Server: Apache
ETag: "0192384-9023-1a929893"
Accept-Ranges: bytes
Content-Length: 33286
Vary: Accept-Encoding,User-Agent
Cache-Control: max-age=7200
Expires: Fri, 30 Dec 2005 19:30:56 GMT
Content-Type: text/xml

Кроме max-age, в заголовке Cache-Control допускается множество других директив. Каждая из них может использоваться либо в запросах, либо в ответах, либо — как max-age — и в запросах, и в ответах.

Директивы, допускаемые в запросах
Директива Описание
no-cache Сервер не должен использовать кэшированный ответ.
no-store Ответ на этот запрос не должен кэшироваться.
max-age=delta-seconds Клиент допускает кэшированный ответ, если его возраст не превышает delta-seconds секунд; клиент не требует его валидации.
max-stale=delta-seconds Клиент допускает кэшированный ответ, если его возраст не превышает delta-seconds секунд.
min-fresh=delta-seconds Клиент допускает кэшированный ответ, если он будет оставаться действительным не менее delta-seconds секунд от момента запроса.
no-transform К запрашиваемому документу не должны применяться преобразования.
only-if-cached Допускается только кэшированный ответ. Если подходящего ответа нет в кэше, то не нужна ни валидация старого ответа, ни получение нового.

Директивы, допускаемые в ответах
Директива Описание
public Ответ разрешается сохранять в любом кэше.
private Ответ разрешается сохранять только в закрытом кэше (т.е. только для этого пользователя).
no-cache Кэшированный ответ не должен использоваться без предварительной его валидации.
no-store Ответ не разрешается сохранять в кэше.
no-transform К передаваемому документу не должны применяться преобразования.
must-revalidate Если кэшированный ответ устарел, то к нему должна применяться процедура валидации. Эта директива отменяет действие max-stale.
max-age=delta-seconds Клиент допускает кэшированный ответ, если его возраст не превышает delta-seconds секунд; клиент не требует его валидации.
s-maxage=delta-seconds То же, что max-age, но действует только на открытые кэши.
proxy-revalidate То же, что must-revalidate, но действует только на прокси-сервера.

Рассмотрим примеры использования заголовка Cache-Control:

Cache-Control: private, max-age=3600

Допустимо в ответе от сервера; означает, что ответ может храниться только в закрытом кэше на протяжении часа.

Cache-Control: public, must-revalidate, max-age=7200

Означает, что ответ может храниться в открытом кэше на протяжении двух часов; после их истечения действительность сохранённой копии должна перепроверяться при каждом запросе.

Cache-Control: must-revalidate, max-age=0

Вынуждает клиента перепроверять действительность сохранённой копии при каждом запросе; max-age=0 указывает, что документ устаревает сразу же после его получения клиентом. В заметке Марка Ноттингема «Leveraging the Web: Caching» приведён любопытный пример случая, в котором можно использовать такой заголовок.

Cache-Control: no-cache

Практически то же самое, что и в предыдущем примере; единственное отличие — что клиент мог использовать в запросе директиву max-stale и получить устаревший ответ, тогда как директива must-revalidate отменила бы действие max-stale. Такая сложная система необходима потому, что, как я отмечал раньше, и сервер, и клиент могут одновременно влиять на использование кэша.

Все эти примеры использования заголовка Cache-Control рассматривают его применение в ответе от сервера. Теперь рассмотрим примеры, когда этот заголовок используется в запросе.

Cache-Control: no-cache

Вызывает «полное» («end-to-end») обновление документа: кэш, к которому обращается клиент, должен вновь запросить документ у сервера, с которого он был получен.

Cache-Control: min-fresh=200

Таким заголовком клиент запрашивает документ, который будет оставаться действительным на протяжении 200 секунд после запроса.

Vary

Наверное, вам интересно, не запутывается ли кэш во всех этих ситуациях. Например, что он должен делать, если сервер может возвращать различные документы для одного и того же URI? Для таких случаев в HTTP предусмотрен заголовок Vary. Этот заголовок указывает кэшу названия всех тех заголовков запроса, при изменении которых сервер вернул бы другой документ.

Предположим, что сервер согласовывает с клиентом тип возвращаемого документа — тогда разные ответы для одного и того же URI могли бы иметь разный заголовок Content-Type, в зависимости от согласованного типа документа. Тогда сервер может добавить в ответ заголовок Vary: accept, и кэш сохранял бы отдельно ответы на запросы с разными значениями заголовка Accept.

Date: Mon, 23 Jan 2006 15:37:34 GMT
Server: Apache
Accept-Ranges: bytes
Vary: Accept-Encoding,User-Agent
Content-Encoding: gzip
Cache-Control: max-age=7200
Expires: Mon, 23 Jan 2006 17:37:34 GMT
Content-Length: 5073
Content-Type: text/html; charset=utf-8

В этом примере сервер указывает, что ответ можно сохранять в кэше на протяжении двух часов, причём по отдельности для запросов с разными значениями заголовков Accept-Encoding и User-Agent.

Connection

После того, как сервер успешно проверит действительность кэшированного результата, например при помощи валидатора в заголовке If-None-Match, — он возвращает код 304 Not Modified. Больше в этом случае ничего не происходит, так? На самом деле, не совсем: сервер может послать вместе с ответом 304 Not Modified значения заголовков документа, чтобы обновить их в кэше. Кроме этого, сервер может в заголовке Connection перечислить названия тех заголовков, которые не надо обновлять в кэше.

По умолчанию, некоторые заголовки не сохраняются в кэше — это так называемые «заголовки соединения» («hop-by-hop»): Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, TE, Trailers, Transfer-Encoding и Upgrade. Значения всех остальных заголовков — «заголовков документа» («end-to-end») — по умолчанию кэшируются.

HTTP/1.1 304 Not Modified
Content-Length: 647
Server: Apache
Connection: close
Date: Mon, 23 Jan 2006 16:10:52 GMT
Content-Type: text/html; charset=iso-8859-1

...

В этом примере значение заголовка Date, не являющегося заголовком соединения и не указанного в заголовке Connection, будет сохранено в кэше.

Если б только всё было так просто

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

Используемая в HTTP 1.0 модель кэширования построена вокруг времени устаревания документа. Валидатор Last-Modified проверяет время последнего изменения документа. Кэш использует для проверки действительности документа заголовки Date, Expires, Last-Modified и If-Modified-Since.

Если вы разрабатываете HTTP-клиент, то лучше всегда использовать оба этих валидатора, потому что неизвестно заранее, не окажется ли на пути между вами и сервером кэш, работающий по протоколу HTTP 1.0. Безусловно, все эти старые сервера уже давно могли бы обновить — ведь от даты опубликования протокола HTTP 1.1 прошло семь лет, — но они всё ещё попадаются. Рекомендуемая мной тактика даёт большую гарантию успеха, чем использование средств только какой-то одной версии HTTP, — всё равно, как если бы вы носили одновременно и ремень, и подтяжки.

Теперь, когда вы понимаете, как работает кэширование в HTTP, вам наверняка интересно, нет ли для вашего любимого языка готовых библиотек с поддержкой кэширования. Я могу ответить про Python: к сожалению, пока что нет. Мне очень обидно, что ни одна из лучших реализаций HTTP-клиентов не написана на моём любимом языке. И нужно это недоразумение исправить.

httplib2

Я рад представить вам библиотеку httplib2 — полнофункциональный HTTP-клиент, написанный на Python. Эта библиотека поддерживает локальный закрытый кэш и понимает все описанные в этой статье операции над ним. Кроме этого, в ней есть много возможностей, обычно отсутствующих в других HTTP-библиотеках:

HTTP и HTTPS
Поддержка HTTPS доступна только в том случае, если модуль работы с сокетами скомпилирован с поддержкой SSL.
Устойчивые соединения
Поддерживается появившийся в HTTP 1.1 заголовок Connection: Keep-alive, позволяющий через одно соединение выполнить несколько HTTP-запросов.
Защита паролем
Как в HTTP, так и в HTTPS поддерживаются три типа HTTP-авторизации:
Кэширование
Эта библиотека позволяет работать с любым закрытым кэшем, понимающим заголовок Cache-Control, причём используется оба валидатора — и ETag, и Last-Modified.
Все методы
Эта библиотека позволяет использовать в запросах любые HTTP-методы, а не только GET и POST.
Перенаправление
При использовании метода GET автоматически обрабатываются перенаправления (коды ответа 3ХХ).
Сжатие
Поддерживаются два типа сжатия, compress и gzip.
Обработка потерянных обновлений
Заголовки ETag автоматически вставляются в запросы PUT к документам, имеющимся в кэше, — в исполнение раздела 3.2 статьи « Обнаружение потерянных обновлений при помощи немонопольной блокировки».
Поблочная проверка
Библиотека прошла большой (и всё ещё растущий) набор поблочных тестов.

Об остальных возможностях вы можете узнать на странице проекта httplib2.

В следующий раз

В следующей статье я рассмотрю HTTP-авторизацию, перенаправления, устойчивые соединения и сжатие в HTTP-ответах, а также поддержку всех этих возможностей в httplib2. Ещё вам наверняка будет интересно, как реализуют кэширование «крутые парни», — этому будет посвящена целиком отдельная статья.



XML.com Copyright © 1998-2007 O'Reilly Media, Inc.
Перевод: xmlhack.ru Copyright © 2000-2007 xmlhack.ru