Application Cache API — новые возможности и проблемы

в 19:58, , рубрики: html, javascript

Голая баба. ШуткаПостепенно концепция стандарта HTML5 становиться реальностью. Браузеры начинают поддерживать новые возможности, которых так не хватало. Но с новыми возможностями появляются и новые проблемы.
В данной статье рассматривается Application Cache API — совокупность функций, обеспечивающих продвинутое кэширование ресурсов web-приложения, и с помощью которых можно просматривать загруженные ранее сайты без подключения к сети Интернет. Особое внимание я уделил практическому использованию и проблемам Application Cache.

Далее по статье под словами «кэш» и его производными имеется в виду Application Cache и работа с ним. Стандартный кэш браузера обозначается как «стандартный кэш».

Общая схема работы Application Cache

Механизм работы Application Cache на первый взгляд прост. Когда пользователь первый раз пользуется сайтом, страницы запоминаются в кэше браузера. Затем при последующих посещениях, а также когда соединение с Интернет теряется, используются заранее сохраненные данные в специальном «хранилище». Контроль над кэшированием сайта производится с помощью специального файла, названного manifest. В некоторых статьях используется .apppcache вместо .manifest. В данной статье используется .manifest. Хотя можно использовать и свое название.

Хранилище

«Хранилище» — условное название, используемое в браузерах для обозначения места хранения Application Cache. Механизмы работы Application Cache и стандартного кэша браузера различны, поэтому требуют отдельного размещения. Отличия механизмов работы между двумя видами кэширования следующие:

  • Данные помещённые в стандартный кэш могут быть автоматически, без команды со стороны пользователя или сервера, удаленны при его заполнении или истечения стока действия, указанного в заголовках файлов. Данные помещённые в хранилище Application Cache могут быть удалены только по команде пользователя или сервера.
  • В стандартный кэш попадают только файлы, загруженные в процессе просмотра страницы. В Application Cache можно поместить любые файлы, загружаемые с сервера согласно инструкции manifest.

Данные различия оказывают существенное влияние на механизмы кэширования, которые будут подробно рассмотрены в конце статьи.

Подключение файла .manifest

Первоначально необходимо «включить» Application Cache. Это делается путём указания браузеру на файл .manifest с помощью атрибута manifest в теге html:

<!DOCTYPE html>
<html  manifest="cache.manifest">

Здесь существуют две особенности:

  • Application Cache работает только если браузер обрабатывает страницу как HTML5-документ. Поэтому DOCTYPE желательно указывать. Без указанного DOCTYPE могут наблюдаться проблемы в неактуальных версиях браузеров.
  • Название файла может быть произвольным.

После этого достаточно только предоставить пользователю первый файл .manifest, но и тут нас ожидает два важных момента:

  • Нужно определить MIME type для файла .manifest. Для этого в файле .htaccess на Вашем сервере необходимо дописать строчку, которая определяет новый тип файлов:
    AddType text/cache-manifest .manifest
    
  • У браузера Mozilla FireFox есть проблема с обновлением содержания файла manifest, поэтому специально для него нужно дописать конструкцию в .htaccess:
    <IfModule mod_expires.c>
    	ExpiresActive On
    	ExpiresByType text/cache-manifest "access plus 0 seconds"
    </IfModule>
    

Проделав эти операции, мы можем заняться содержимым файла .manifest.

Файл .manifest

Приведём пример такого файла:

CACHE MANIFEST
# Версия 1

images/logo.png

CACHE:
css/default.css

NETWORK:
index.php

FALLBACK:
/ /offline.html

Первая строчка содержит текст CACHE MANIFEST — она означает, что данный файл является файлом manifest. Она обязательна.
Комментарии в файле обозначаются с помощью символа решетки #.
Пустые строки или строки с пробелами игнорируются и используются только для логического разделения.
В примере за комментарием следует, так называемое, «явное вхождение» (images/logo.png) – аналог раздела CACHE. Может использоваться, как и совместно с этим разделом, так и замещая его.
Каждое правило (ресурс) должно быть прописано на отдельной строке.
Далее следуют три раздела. Название разделов, как и текст CACHE MANIFEST, должны быть в верхнем регистре. Каждый раздел выполняет свои функции. Порядок следования разделов не важен. Должен быть хотя бы один раздел. Разделы могут быть пустыми.
Давайте разберем каждый раздел.

Раздел CACHE

Этот раздел является разделом по умолчанию, т.е. если в файле .manifest нет никаких заголовков («явное вхождение»), то содержание этого файла принимается за содержание раздела CACHE. Внутри этого раздела размещаются пути или URI к кэшируемым ресурсам. Существуют следующие особенности:

  • Указывается конкретный ресурс (файл), т.е. Вы не можете писать в этом разделе строку вида /images/*
  • Страница, содержащая определение файла .manifest (страница с ), кэшируется автоматически. Можно добавить атрибут manifest для всех страниц, что бы кэшировать весь сайт, без их перечисления в файле .manifest. Замечание: Mozilla Firefox не кэширует динамически созданную страницу (.php) с атрибутом manifest.

Раздел NETWORK

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

CACHE MANIFEST

NETWORK:
*

Это разрешит загрузку всех файлов, которые не имеют сохраненной копии. Если требуются какие-то ограничения на загрузку, то можно указывать конкретные файлы (например, index.php) и директории (например, /images/*).

Раздел FALLBACK

В этом разделе указывается, что браузеру в offline-режиме отображать при обращении к страницам, которые не были кэшированы. Запись правил осуществляется по следующему принципу:
запрашиваемая_страница отображаемая_страница
Разделять пути можно и пробелами и табуляцией.
Относительно этого раздела существую следующие особенности:

  • Можно для указания запрашиваемой страницы использовать паттерны. В первоначальном примере мы устанавливаем отображение страницы offline.html для всех не кэшированных файлов:
    	CACHE MANIFEST
    
    	FALLBACK:
    	/ /offline.html
    

    Можно использовать указания на конкретные файлы или файлы с определённым расширением (например, *.html /offline.html), что очень удобно.

  • Отображаемые страницы кэшируются автоматически и указывать их в разделе CACHE не нужно.

Замечания по разделам NETWORK и FALLBACK

Существует три замечания, которые нельзя опустить:

  • Все файлы, не обозначенные в разделах NETWORK и FALLBACK, и не имеющие сохранённых копий в хранилище Application Cache, загружаться не будут, даже если есть копии ресурсов в стандартном кэше браузера. Поэтому, если Вы не используете общие паттерны (* или /), то не забывайте указывать все файлы необходимые приложению, иначе вы не сможете работать с ними. Использовать общий паттерн достаточно в одном из разделов.
  • Правила разделов NETWORK и FALLBACK не перекрывают правила раздела CACHE («явного вхождения»), то есть при работе без сети сначала грузятся кэшируемые данные, а только потом определяется политика в отношении ресурсов, указанных в этих двух разделах. На практике это приводит к усложнению структуры приложения.
  • Правила этих разделов не действуют на страницу, содержащую определение файла .manifest – она всегда автоматически кэшируется.

Application Cache API

При объявлении атрибута manifest у нас появляется возможность работать с объектом управления кэшем для данного документа window.applicationCache. Совокупность функций и методов работы с этим объектом и образуют Application Cache API.
Рассмотрим этот объект подробно.

Методы объекта applicationCache

Обращение к объекту выглядит следующим образом:

window.applicationCache

Теперь сами методы.

window.applicationCache.status

Метод возвращает числовое значение соответствующее статусу состояния кэша. Возможны следующие статусы:

  • UNCACHED – кэш ещё не инициализирован (числовое значение 0);
  • IDLE – никаких действий с кэшем не производиться (числовое значение 1);
  • CHECKING – производиться проверка файла .manifest (числовое значение 2);
  • DOWNLOADING – производится загрузка ресурсов для помещения их в кэш (числовое значение 3);
  • UPDATEREADY – загрузка необходимых ресурсов выполнена и требуется их инициализация при помощи метода swapCache()(числовое значение 4);
  • OBSOLETE – текущий кэш является устаревшим (числовое значение 5).

Так же в объекте applicationCache определены константы состояния (например, cache.IDLE равно 1). Так что числовые значения необязательно запоминать.

window.applicationCache.update()

Метод инициирует процесс проверки файла .manifest и последующие скачивание необходимых ресурсов.

window.applicationCache.swapCache()

Метод переключает браузер на использование новых кэшированных файлов вместо старых. Перерисовки страницы не происходит, только при последующем обращении к кэшированным файлам они берутся уже из обновлённого кэша.
Простой альтернативой метода является перезагрузка страницы, например, при помощи location.reload().

События объекта applicationCache

С объектом applicationCache связанны следующие события:

  • cached – происходит при формировании первого кэша в хранилище;
  • checking – происходит при отправке запроса получения файла .manifest;
  • downloading – происходит при загрузке ресурсов в кэш;
  • progress – происходит при загрузке каждого ресурса по отдельности;
  • error – произошла ошибка при обращении к файлам ресурсов или файлу .manifest;
  • noupdate – происходит при подтверждение, что файл .manifest не обновился;
  • obsolete – происходит при подтверждение, что кэш в хранилище устарел и будет удалён;
  • updateready – происходит при окончании загрузки обновлённого кэша.

Детальная схема работы Application Cache

Всё вроде бы хорошо и складно, но как это работает в реальности? Сейчас мы с Вами разберёмся с этим вопросом.
Первое, что следует рассмотреть, это дельная пошаговая схема работы Application Cache:

  • Когда браузер в первый раз загружает документ с указанием атрибута manifest, то происходит дополнительный запрос и получение файла .manifest (событие checking). При первом посещении, когда кэша этого документа ещё не существует, происходит загрузка в кэш всех файлов согласно правилам, указанным в файле .manifest (событие downloading). Файлы могут загружаться из стандартного кэша браузера, что эффективно при изменении правил. В итоге создаётся первое хранилище данных кэша manifest (событие cached).
  • При повторном обращении к тому же документу он загружается из кэша, так как был ранее кэширован (документы с атрибутом manifest всегда кэшируются). Обращения к серверу в этот момент не происходит. Объект управления кэшем для данного документа window.applicationCache после загрузки документа инициирует проверку изменения файла .manifest и посылает запрос на сервер (событие checking). Сервер отвечает об изменении.
  • Если файл .manifest не изменился (событие noupdate), то загрузка выполнена и приложение продолжит работу в намеченном направлении. Запросов к серверу на получение кэшированных файлов не производиться.
  • Если файл .manifest изменился, то выполняется стандартная загрузка документа и начинается фоновая загрузка кэша (событие downloading). После выполнения загрузки (событие updateready) перерисовки страницы не происходит. Обновленные данные кэша будут использоваться только при следующей загрузке документа.
  • Если при загрузке файла .manifest произошла ошибка или он содержит ошибки, например, ссылка на несуществующий файл (событие error), то загрузка производиться обычном режиме с использованием сохранённых копий ресурсов. При этом кэш не затрагивается и браузер его не использует.

Обновление кэша

Отдельно необходимо рассмотреть вопрос об обновлении (очистке) кэша. Из анализа детализированной схемы работы Application Cache видно, что изменение на сервере данных не приводит к автоматическому обновлению этих данных в кэше. Обновление кэша можно произвести следующими способами:

  • Пользователь вручную удаляет данные старого кэша.
    Следует понимать, что Application Cache и стандартный кэш браузера – это не одно и тоже. Как было сказано выше, оба механизма кэширования отличаются алгоритмами работы, функционалом и местом хранения. Произведя очистку стандартного кэша, мы не затронем Application Cache. Для очистки Application Cache требуется воспользоваться функциями очистки «Хранилища» – нового пункта настроек у современных браузеров.
    Как очистить хранилище в браузере Mozilla Firefox можно прочитать здесь — support.mozilla.com/ru/kb/okno-nastrojki-panel-dopolnitelnye#w_ahaalaklai-eaulikioi.
    О том, как это осуществляется в Opera — help.opera.com/Windows/11.50/ru/storage.html.
    В браузере Google Chrome очистить кэш простыми способами невозможно. Для этого надо использовать страницу управления chrome://appcache-internals/.
    В Safari кэш очищается вместе со стандартным кэшем браузера. Смотрите справку.
  • Внеся изменения в файл .manifest. Любые изменения инициируют скачивание всех ресурсов кэша и последующее его обновление. Изменения вступят в силу после последующей перезагрузки страницы. Например, это можно сделать, изменив несколько закомментированных символов (мы изменили версию нашего первоначального примера):
    	CACHE MANIFEST
    	# Версия 2
    
  • Программное обновление с использованием Application Cache API. Этот способ будет разобран ниже в примере.

Для использования в реальных проектах подходят лишь последние два способа, которые были бы достаточно удобны, если бы не «агрессивное кэширование» браузера Mozilla Firefox.

Скрытая проблема обновления

Браузер Mozilla Firefox имеет «своеобразный» механизм обновления кэша, известный как «агрессивное кэширование». Проблема проявляется, когда мы совмещаем стандартное кэширование и Application Cache, а нам их совмещать необходимо, если мы хотим иметь кэширование в старых браузерах.
Дело в том, что при обновлении Application Cache в Mozilla Firefox данные также берутся из стандартного кэша без проверки изменений на сервере. Это приводит к тому, что в обновлённый Application Cache могут попасть устаревшие данные из стандартного кэша, т.е. обновление будет выполнено, но в кэше будит старые версии файлов. Данный факт ставит все преимущества работы с Application Cache под сомнение.
Корректными способами обновления кэша в такой ситуации являются:

  • изменение имени файла;
  • 2-хступенчитое обновление.

В первом случаи мы просто меняем имя измененного файла во всех ресурсах, где он упоминается, в том числе и файле .manifest. Файла с первоначальным именем существовать не должно(!). В результате при очередной проверке изменения произойдёт ошибка загрузки несуществующего файла, и ресурсы кэша будут загружены с сервера.
Если вариант с изменением имени файла не подходит в силу тех или иных причин, то необходимо воспользоваться 2-хступенчитым обновлением кэша.
На первом этапе обновления мы исключаем изменённый файл из раздела CACHE файла .manifest и устанавливаем в заголовке отдаваемого файла старевший срок годности:

header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); 
header('Cache-Control: no-store, no-cache, must-revalidate'); 
header('Pragma: no-cache');

В результате у активных пользователей данный файл обновиться в стандартном кэше, а из Application Cache будет удалён.
На втором этапе мы уже включаем измененный файл в раздел CACHE, и он успешно кэшируется. К сожалению, данное обновление коснется только активных пользователей, поэтому старайтесь использовать переименование файлов.

Привязка к домену

Ещё один важный момент, о котором нужно упомянуть – кэш привязан к домену, а не к странице, где объявлен файл .manifest. Сохранённые в кэше, ресурсы будут использоваться для всех страниц данного домена, даже если в них нет ссылки на файл .manifest. Это достаточно удобно – можно на стартовой странице определить кэшируемые ресурсы, а потом не беспокоится об их кэшировании на других страницах.
Если же мы используем несколько разных файлов .manifest для разных страниц одного домена, то они будут перекрывать друг друга. В кэше будут храниться только те ресурсы, которые были указанны в последнем загруженном файле .manifest.
Также при использовании нескольких файлов .manifest можно создать ситуацию, когда сохраняемые страницы циклически кэшируют друг друга. Возникновение такой ситуации приводит приложение в не рабочие состояние. Учитывая, выше написанное, следует отметить, что использовать несколько файлов .manifest непрактично и рискованно, хотя такая возможность существует.
Из привязки к домену также следует, что Application Cache работает во фреймах, используя кэш домена фрейма. Этот момент по достоинству должны оценить разработчики игр.

Работа в offline

Ещё одним несомненным преимуществом Application Cache является возможность работы в режиме offline. Только представьте себе – у клиента временно перестала работать сеть, но он всё равно может осуществлять работу с вашим ресурсом.
Но даже здесь не всё гладко. Вспомним одну особенность Application Cache – страница, на которой объявлен файл .manifest всегда попадает в кэш. В результате получается, что страница будет загружена, руководствуясь разделом CACHE, а не FALLBACK. То есть приложение будет вести себя, так как будто связь есть. Но как определить, что соединение отсутствует?
Для этого в спецификации HTML5 определенны два события online и offline, которые вызываются при создании и разрыве соединения соответственно. Но пока они ни в одном из браузеров не работают. Также можно воспользоваться свойством online объекта navigator:

window.navigator.onLine

Это свойство возвращает true при наличии соединение и false при его отсутствии. Получается, что при инициализации страницы нужно проверять это свойство и далее строить работу на основе его значения. На данный момент времени свойство navigator.onLine поддерживают браузеры Mozilla Firefox 2, Internet Explorer 4 и старше.
Пример использования:

<!DOCTYPE HTML>
<html lang="ru" manifest="cache.manifest">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<title>Application Cache Test</title>
</head>

<body>
<style>
	html {font-family: "DejaVu Sans", "Geneva CY", "Verdana"; background: #FFFFFF; OVERFLOW: hidden; ; border: 0;}
	body {width: 880px; height: 600px; position: relative; background: #999999; margin: 20px auto; box-shadow: 0 0 15px 10px #999999; -webkit-box-shadow: 0 0 15px 10px #999999; -moz-box-shadow: 0 0 15px 10px #999999;}
	/* Экран загрузки*/
	.progressbar {display:none; position:absolute; left:0px; top:0px; width:880px; height:600px; background-color:#333333; z-index:256; border:1px solid #333333;}
	.progressbar #progresstext {position:absolute; left:0px; top:200px; width:880px; color:#66FF00; text-align:center; font-size:36px; text-shadow:0 0 0.8em #AAFF00, 0 0 0.8em #AAFF00;}
	.progressbar #progress {position:absolute; left:100px; top:300px; width:600px; height:40px;}
	/* Заставка */
	.flash {position:absolute; left:0px; top:0px; width: 880px; height: 600px; background-color:#666666; z-index:51; border: 1px solid #333333;}
</style>
<!-- Блокирующее окно -->
<div id="flash" class="flash"></div>
<!-- Окно загрузки -->
<div id="progressbar" class="progressbar"><div id="progresstext">Загрузка</div><progress id="progress"></progress></div>
<script type="text/javascript" src="js/jquery-1.5.min.js"></script>
<script type="text/javascript">
	// Переменные прогресса
	var progress_value = 0;
	var progress_max = 1;
	$(function() {
		// Проверяем подключение
		if (navigator.onLine) {
			alert('Соединение есть');
		}
		else {
	      alert('Невозможно установить соединение с сервером');
		}
		// Получаем объект Application Cache
		cache = window.applicationCache;
		if (cache) {
			// Добавляем слушателей событий
			// Ресурсы уже кэшированнны. Индикатор прогресса скрыт.
			cache.addEventListener('cached', function(e) {ProgressHide();}, false);
			// Начало скачивания ресурсов. progress_max - количество ресурсов. Показываем индикатор прогресса
			cache.addEventListener('downloading', function(e) {ProgressShow(); progress_max = 3;}, false);
			// Процесс скачивания ресурсов. Индикатор прогресса изменяется
			cache.addEventListener('progress', function(e) {ProgressChange();},	false);
			// Скачивание ресурсов. Скрываем индикатор прогресса. Обновляем кэш. Перезагружаем страницу.
			cache.addEventListener('updateready', function(e) {ProgressHide(); window.applicationCache.swapCache(); location.reload();}, false);
		}
	});
	// Отслеживаем нажатие клавиш клавиатуры
	$(document).keyup(function(event){
		// При нажатии shift+1 производиться попытка обновления кэша
		if (event.shiftKey && event.keyCode == 49) {
	      window.applicationCache.update();
		}
	   return false;
	});

	//------------------- Функции управлением экраном загрузки ----------------//
	function ProgressShow() {
		$("#progressbar").show(300);
		progress_value = 0;
	}

	function ProgressChange() {
		progress_value++;
		$("#progress").attr({max: progress_max, value: progress_value});
	}

	function ProgressHide() {
		$("#progressbar").hide(300);
	}
	//-------------------------------------------------------------------------//
</script>
</body>
</html>

Возможности и проблемы использования Application Cache

Описанной выше информации достаточно, что бы понять как работает Application Cache. Можно сделать первые выводы о возможностях и трудностях.
К возможностям и преимуществам Application Cache относятся:

  • Увеличение скорости загрузки страниц, за счёт того, что не требуется тратить время на скачивание файлов, которые были кэшированы.
  • Снижение нагрузки на сервер, так как вместо многочисленных запросов к ресурсам (имеются в виду кэшируемые на клиенте данные) для проверки их изменения, мы имеем всего лишь один запрос к файлу .manifest.
  • Возможность кэшировать файлы по заранее определённым правилам.
  • Постоянное хранение файлов, с жестким контролем изменения.
  • Возможность работать с приложением в offline-режиме.
  • Кэш работает во фреймах.

К проблемам использования Application Cache относятся:

  • При первой загрузке документов после отображения целевой страницы автоматически начинается фоновая загрузка кэшируемых документов. На слабом канале активная работа с приложением после первой загрузки может превратиться в «кошмар». Особенно, если объём и количество файлов большое.
  • Существует проблема синхронизации данных на сервере и клиенте. Изменение данных на сервере не приводит к изменению данных, хранящихся в кэше клиента. Необходимо инициировать процедуру обновления. До этого момента браузер будет загружать старые версии ресурсов.
  • После обновления кэша сохранённые данные будут использоваться после перезагрузки страницы. Это приводит к тому, что даже с изменённым файлом .manifest первоначальная загрузка происходит с использованием старого кэша.
  • Можно привести приложение в нерабочее состояние, если не внести в разделы NETWORK и FALLBACK правила для файлов, необходимых для работы этого приложения.
  • При использовании нескольких файлов .manifest можно создать ситуацию цикличного кэширования и привести приложение в нерабочее состояние.
  • Правила раздела FALLBACK для работы в режиме offline не перекрывают правила раздела CACHE. На практике это приводит к усложнению структуры приложения для работы в режиме offline.
  • Страница, содержащая определение файла .manifest, кэшируется автоматически. Что не позволяет легко её заменить в offline режиме.

Так же следует обратить внимание на следующие нюансы:

  • Ресурсы, полученные POST-запросом, не кэшируются и не берутся из кэша.
  • По умолчанию размер кэшированных данных ограничивается 50 Мб. Для старых версий Mozilla Firefox и Google Chrome ограничение 5 Мб. Так же у браузера Opera размер возможного кэша можно увеличить в настройках.
  • Кодировка файла .manifest может быть любая, но, если Вы не хотите проблем с кириллическими символами, следует использовать кодировку utf-8.
  • В старых версиях браузеров Application Cache не работает. Application Cache поддерживается браузерами Google Chrome 4.0, Mozilla Firefox 3.5, Internet Explorer 10, Opera 10.6, Opera Mobile 11, Safari 4 и старше.
  • В некоторых версиях браузеров при первом обращении для данного домена к Application Cache появляется всплывающее системное окно вопроса о разрешении использовать «хранилище». При отрицательном ответе Application Cache для данного домена не используется. Из современных браузеров такое всплывающее окошко отображает Mozilla Firefox.
  • Application Cache игнорирует установки сервера для обычного кэширования, например, игнорируется Cache-Control: no-store.

И что же мы получаем в итоге?

Несмотря на целую кучу проблем использования, Application Cache — это мощный механизм оптимизации для нагруженных систем. Особенно он будет полезен для игр и интерактивных приложений, использующих FullAjax. Правильное использование Application Cache позволит не только увеличить скорость загрузки страниц пользователям и уменьшить количество трафика, но и снизить нагрузку на Ваш сервер, т.к. кэшированные страницы загружаются не с сервера, а с локального хранилища.

Надеюсь, что эта статья позволит Вам открыть для себя всю мощь Application Cache.

Автор: VitaZheltyakov

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js