foo.v2.gif foo.gif
/css/main.v1.27.css css/main.css
/javascript/md5.v6.js /javascript/md5.js
Когда это правило работает, мы можем менять URL (добавляя к нему суффикс версии), не меняя расположения файла на диске. Обнаружив, что URL изменился, браузер считает, что ему нужно обратиться к новому ресурсу.
Вы можете поинтересоваться, почему мы просто-напросто не использовали динамические ссылки (например, /css/main.css?v=4)? Дело в том, что, согласно спецификации HTTP, пользовательские агенты вообще не должны кэшировать такие URL. И хотя IE и Firefox это игнорируют, Opera и Safari точно следуют букве – поэтому, чтобы гарантировать корректную работу всех браузеров при кэшировании наших ресурсов, мы избегаем использовать динамические ссылки.
Теперь, когда мы научились менять URL без перемещения файла, было бы неплохо автоматизировать обновление URL. В небольшой рабочей среде (или в среде разработки, для тех, кому приходится иметь дело с большими рабочими средами) это довольно легко осуществить с помощью функций шаблона. Следующий пример сделан на основе Smarty, но может быть реализован и на основе других подобных движков:
<link l:href="{version src=’/css/group.css’}" rel="stylesheet" type="text/css" />
function smarty_version($args){
$stat = stat($GLOBALS[‘config’][‘site_root’].$args[‘src’]);
$version = $stat[‘mtime’];
echo preg_replace(‘!.([a-z]+?)$!’, «.v$version.$1», $args[‘src’]);
<link l:href="/css/group.v1234567890.css" rel="stylesheet" type="text/css" />
Для каждого залинкованного ресурса мы определяем местоположение файла на диске, проверяем его mtime (дату последнего изменения) и вставляем эту информацию в URL в виде номера версии. Это прекрасно работает на сайтах с низким трафиком (где метод stat, возвращающий информацию о файлах и каталогах, обходится довольно дешево) и в среде разработчика, однако плохо масштабируется на большие сайты, поскольку каждый вызов stat означает обращение к диску на чтение.
Решить эту проблему нетрудно. В больших системах у нас изначально есть номер версии для каждого ресурса в виде номера, присвоенного системой контроля версий (вы же используете систему контроля версий, правда?). На этапе финального построения нашего сайта требуется лишь проверить номера версий всех нужных файлов и записать их в статический конфигурационный файл.
$GLOBALS[‘config’][‘resource_versions’] = array(
‘foo.gif’ => ‘2.1’,
‘/css/main.css’ => ‘1.27’,
‘/javascript/md5.js’ => ‘6.1.4’,
Затем мы меняем нашу функцию в шаблоне, чтобы она могла использовать эти номера версий при реальной работе.
function smarty_version($args){
if ($GLOBALS[‘config’][‘is_dev_site’]){
$stat = stat($GLOBALS[‘config’][‘site_root’].$args[‘src’]);
$version = $stat[‘mtime’];
$version = $GLOBALS[‘config’][‘resource_versions’][$args[‘src’]];
echo preg_replace(‘!.([a-z]+?)$!’, «.v$version.$1», $args[‘src’]);
Таким образом, нам не нужно переименовывать файлы или даже запоминать, когда мы изменяли их содержимое, – URL автоматически будут изменяться каждый раз, когда мы перерабатываем сайт. В общем, почти приехали.
Собираем все вместе
В разговоре об отправке заголовков, обеспечивающих долговременное хранение статических ресурсов в кэше, мы отметили, что если отдача контента реализована не на PHP, то простого способа добавления таких заголовков не существует. Чтобы справиться с этой проблемой, мы можем либо все же использовать PHP, либо переложить задачу модификации заголовков на Apache.
Использовать для нашей цели PHP нетрудно. Мы просто должны изменить правила rewrite для статических файлов, чтобы они проходили через PHP-скрипт, и написать сам скрипт, который будет выдавать нужные заголовки, перед тем как передать эти файлы по запросу.
RewriteRule ^/(.*.)v[0-9.]+.(css|js|gif|png|jpg)$ /redir.php?path=$1$2 [L]
header(«Expires: „.gmdate(„D, d M Y H:i:s“, time()+315360000).“ GMT»);
header(«Cache-Control: max-age=315360000»);
# ignore paths with a ‘..’
if (preg_match(‘!..!’, $_GET[path])){ go_404(); }
# make sure our path starts with a known directory
if (!preg_match(‘!^(javascript|css|images)!’, $_GET[path])){ go_404(); }
# does the file exist?
if (!file_exists($_GET[path])){ go_404(); }
# output a mediatype header
$ext = array_pop(explode(‘.’, $_GET[path]));
switch ($ext){
case ‘css’:
header(«Content-type: text/css»);
case ‘js’ :
header(«Content-type: text/javascript»);
case ‘gif’:
header(«Content-type: image/gif»);
case ‘jpg’:
header(«Content-type: image/jpeg»);
case ‘png’:
header(«Content-type: image/png»);
header(«Content-type: text/plain»);
# echo the file’s contents
echo implode(‘’, file($_GET[path]));
function go_404(){
header(«HTTP/1.0 404 File not found»);
Несмотря на то что такой подход работает, это не лучшее решение. PHP в сравнении с Apache требует больше памяти и времени на исполнение. Кроме того, нам необходимо соблюдать осторожность из-за возможных эксплойтов. Дабы избежать всей этой головной боли, мы можем попытаться использовать Apache напрямую. Директива RewriteRule позволяет нам устанавливать значения переменных окружения при срабатывании директивы, тогда как директива Header добавляет заголовки лишь в том случае, когда присвоенно значение заданной переменной. Комбинируя две эти директивы, мы легко можем составить нужную цепочку инструкций:
RewriteEngine on
RewriteRule ^/(.*.)v[0-9.]+.(css|js|gif|png|jpg)$ /$1$2 [L,E=VERSIONED_FILE:1]
Header add «Expires» «Mon, 28 Jul 2014 23:30:00 GMT» env=VERSIONED_FILE
Header add «Cache-Control» «max-age=315360000» env=VERSIONED_FILE
Из-за порядка исполнения Apache, мы должны добавить строчку Re-writeRule в главный конфигурационный файл (httpd.conf), а не в .htaccess, иначе строчки Header будут исполнены первыми, перед установкой переменной окружения. Сами строчки Header могут быть размещены и в главном конфигурационном файле, и в .htaccess – их местоположение ни на что не влияет.
Сокращенный перевод.
© 2006 Carson Systems, Vitamin
Оригинал: www.thinkvitamin.com/features/webapps/serving-javascript-fast
РЫНКИ: Ну и как тебя называть?
Как это вышло
Причиной такого положения дел стал антимонопольный комитет США. Не стоит сразу представлять себе судебный процесс «Консервативная монополия Unix мешает развитию новой перспективной ОС Windows». Антимонопольный процесс против American Telephone and Telegraph (AT&T) в 1949 году помешал распространению влияния телекоммуникационного гиганта на новорожденные отрасли: AT&T было запрещено заниматься продажей каких бы то ни было компьютерных решений, и рекомендовалось ограничиться телефоном и телеграфом.
Именно поэтому исследовательское подразделение AT&T, Bell Labs, с тех пор прибыли само по себе не приносило: все создаваемые компьютерные технологии свободно лицензировались всем желающим. Если бы не это, еще неизвестно, как сложилась бы судьба созданной в Bell Labs операционной системы Unix. Многие из лицензиатов (в основном, конечно же, университеты) создавали слегка или сильно модифицированные версии для собственных нужд. Одной из этих версий, созданной в университете Беркли, Калифорния ничем не примечательным аспирантом Биллом Джоем, была уготована долгая и извилистая судьба.
В начале 1980-х годов в результате очередного антимонопольного судебного процесса AT&T была разделена на несколько подразделений, в результате чего у корпорации оказались развязаны руки (в смысле получения прибыли от торговли компьютерами). И вновь – тяжело предположить, как бы сложилась судьба Unix без этого события: с одной стороны, агрессивный маркетинг AT&T способствовал быстрому и эффективному распространению ОС; с другой – новая стратегия лицензирования (без исходного кода) положила начало разделению разработки Unix на несколько разных независимых веток и многолетнему противостоянию этих веток (так называемые unix wars).
Одной из «веток» стал разработанный в Беркли «берклиевский набор софта» (Berkeley Software Distribution, BSD), по прежнему распространявшийся свободно и с исходниками. Возможно, именно этот факт повлиял на DARPA[На всякий случай: DARPA – Defense Advanced Research Projects Agency (Агентство перспективных исследований при Министерстве обороны США) – так или иначе поддерживало огромную часть исследований, которые сформировали образ современных IT] при выборе – кому бы дать денег для разработки протокола TCP/IP. Дали. Разработали (4.2BSD, август 1983 года). Этот фактор (совместно со многими другими) повлиял на огромную популярность и быстрое распространение BSD. Ну а Билл Джой, с которого начиналась эта версия Unix, тем временем создал собственную фирму под названием «Солнышко».
Оригинальная Unix System V от AT&T, созданная в Беркли BSD, Sun OS от Sun Microsystem и еще несколько дистрибутивов, основанных на System V и BSD, вступили в сложное взаимодействие, включавшее и конкуренцию, и заимствование полезных фич, и юридические споры – в общем, было весело. Однако все это привело к размытию образа Unix и потере ориентиров в стиле «лебедь, рак и щука». Попытки исправить ситуацию привели к очередному пату: AT&T и Sun скоординировались и создали System V version 4, а многие независимые производители, которым не понравился этот альянс гигантов, объединились в Open Software Foundation[Не путать с Free Software Foundation – ничего общего!] и попытались создать «свой» Unix по имени OSF/1 (в общем, это была неудачная попытка)[Вообще говоря, у них там все еще сложнее получилось: сначала группа независимых поставщиков Unix создала группу по его стандартизации X/Open, в ответ на это объединились AT&T и Sun; а уже это привело к появлению Open Software Foundation].
Этот жизнерадостный период (конец 80-х – начало 90-х), называемый Unix wars, имел много интересных последствий: считается, что пока юниксопроизводители воевали друг с другом, Windows захватил десктопы, а внезапно появившийся Linux – сердца простых юниксоидов, которым скандалы производителей были до фени. Впрочем, в данном контексте нам будут намного интереснее другие последствия противостояния.
Что из этого вышло
А последствия были такие: программистам стало очень печально жить. Как водится, в результате маркетинговых войн (когда каждый стремится сотворить систему круче чем у прочих) совместимость различных клонов Unix пошла на убыль. То есть программа, написанная под какую-нибудь Sun OS, совершенно не обязательно станет работать под какой-нибудь другой BSDI (хотя, по идее, и то и другое – Unix). Менеджерам-маркетологам этот вопрос был, в общем, без разницы («а так даже лучше – чего это наши программы будут работать под ОС конкурента?»), но, к счастью, культура Unix всегда определялась скорее программистами и продвинутыми пользователями, нежели менеджерами.
В 1985 году за дело взялась серьезная организация IEEE, которая ведает большой частью американских и международных стандартов. При участии многих выдающихся профессионалов к 1990 году появился стандарт POSIX (о происхождении аббревиатуры – см. эпиграф; официальная расшифровка – Portable Operating System Interface[X в аббревиатуре – что-то вроде «привет юниксоидам»]).
Основная цель POSIX – обеспечить переносимость (Portable) программ между различными операционными системами, соответствующими стандарту. Причем переносимость обеспечивается на уровне исходного кода – то есть предполагается, что программа попадает на целевую систему в виде исходников, и если программа и ОС POSIX-совместимы, то первая без проблем скомпилируется и заработает на второй.
Соответственно своей цели (обеспечить переносимость прикладных программ), POSIX не предъявляет никаких требований к архитектуре операционной системы. POSIX определяет только взаимодействие между программой и ОС в стиле «системный вызов по имени А с параметром Б должен выполнить В и вернуть результат Г или ошибку Д». Это означает, что «POSIX-совместимая операционная система» не является синонимом «Unix». Например, в исходный код Linux (чтобы там ни думала себе SCO) не входит ни единой строчки из изначального AT&T Unix. Тем не менее программы, работающие в System V или BSD, без проблем запустятся в Linux. Торвальдс достиг этого результата действиями «в лоб»: взял стандарт POSIX и реализовал его. Получилось[Когда Oracle портировала свою БД (уже работавшую на Sun Solaris, наследнике Sun OS) на Linux, кто-то задал оракловским инженерам вопрос типа «и что, трудно было?» Ответ был характерен: «мы запускали make» (в смысле – собрали программу из исходников, и все заработало)].
Я вам больше скажу – Windows NT совместима с одной из частей POSIX (1003.1b, real-time extensions, описывающей переключение процессов, синхронизацию потоков и т. п.). И если скачать с сайта Microsoft набор утилит Services for Unix (SFU) – брюки превращаются… во вполне POSIX-соместимую систему (то есть теоретически любая Unix-программа на Windows+SFU должна собраться и запуститься без проблем). И еще более того – поддержка SFU корпорацией Microsoft как отдельного продукта практически прекращена – потому что он теперь будет входить в стандартную поставку Windows. Такие форточки.
Интерфейс взаимодействия операционной системы и прикладных программ (традиционно называемый OS API, Application Programming Interface) естественно, существует в каждой ОС и в очень большой степени определяет легкость программирования под нее. Использованный при создании POSIX метод «практической стандартизации», когда собираются лучшие из используемых подсистем и объявляются стандартом, показала себя существенно эффективней других вариантов: «теоретической стандартизации» (когда собираются ученые и решают «как будет умнее») и неконтролируемой проприетарной разработки (когда единственная фирма-производитель ОС предоставляет API по мере собственного разумения каждого конкретного отдела).
Образцовым примером проприетарного API является, как несложно предположить, Microsoft Windows API. Его наиболее «любимые» программистами характеристики стали уже притчей во языцех:
Далеко не все API документировано, в результате чего прикладные программы, разработанные Microsoft, имеют возможности по интеграции с ОС, недоступные другим программам.
Непоследовательность, непоследовательность, и еще раз она же. Две функции со схожим назначением могут иметь совершенно несвязанные имена, соглашения о формате параметров, побочные эффекты и т. п.
Увлеченность «новыми» технологиями: часть Windows API основывается, как и POSIX, на базовых типах и принципах языка C; другую часть невозможно использовать без знания Microsoft COM.
И просто непродуманность. Хрестоматийный пример: разрабатывая под Windows программу, работающую в командной строке, задачу вызова другой программы и перехвата ее вывода можно решить одним-единственным вызовом перекочевавшей из POSIX функции popen. Но вот разрабатывающий оконное приложение программист обязан пользоваться уже другим API, простейший пример использования которого занимает около двух страниц кода: инициализация внутренних структур, запрос и установка параметров, подготовка окружения.
К слову, и на Windows API существует международный стандарт ECMA-234; эмулятор WinAPI для POSIX-систем Wine опирается именно на этот стандарт.