Сжатие страниц LiveStreet на лету

В ходе выяснения подробностей сжатия статей на LiveStreet родилось краткое how-to.

Шаг 1. Проверка. Чтобы проверить, сжимаются ли уже у вас сейчас страницы, можно воспользоваться сервисом whatsmyip.org/mod_gzip_test/ Скармливаете ему главную страницу своего сайта (к примеру, livestreet.ru ), какой-либо css (взять можно из тела страницы; к примеру, www.livestreet.ru/templates/skin/new/css/style.css?v=1), какой-либо js (вновь из тела страницы; к примеру, www.livestreet.ru/classes/lib/external/JsHttpRequest/JsHttpRequest.js ). Если сервис ответил на все 3 файла радостной зеленой строкой «такой-то адрес is gzipped» — то все замечательно и вы можете прочитать лишь шаг 4 о упаковке JS. В противном случае читайте ниже все.

Шаг 2. Наличие mod_deflate. Для этого можно создать любой php-файл со следующим содержимым:
<?

phpinfo();

?>

Затем вызываем и ищем, есть ли где-то включение «mod_deflate». Если да — переходим к шагу 5 для включения mod_deflate. Если нет — скорее всего, ваш хостер не поддерживает данное расширение и можно огорчиться. Но некоторое решение есть — сжимать файлы c помощью PHP.

Шаг 3. Сжимаем страницы средствами PHP. Создаем файл includes/gzip.php:
<?php
// gzip.php v1.2 - read http://rm.pp.ru/?1.phpgzip
// released on 2004-05-06, by Roman Mamedov<roman at rm.pp.ru>
// license: do with this code whatever you want.

///// Configuration //////////////////
$PREFER_DEFLATE = false; // prefer deflate over gzip when both are supported
$FORCE_COMPRESSION = false; // force compression even when client does not report support
//////////////////////////////////////

function compress_output_gzip($output) {
	return gzencode($output);
}

function compress_output_deflate($output) {
	return gzdeflate($output, 9);
}

if(isset($_SERVER['HTTP_ACCEPT_ENCODING']))
	$AE = $_SERVER['HTTP_ACCEPT_ENCODING'];
else
	$AE = $_SERVER['HTTP_TE'];

$support_gzip = (strpos($AE, 'gzip') !== FALSE) || $FORCE_COMPRESSION;
$support_deflate = (strpos($AE, 'deflate') !== FALSE) || $FORCE_COMPRESSION;

if($support_gzip && $support_deflate) {
	$support_deflate = $PREFER_DEFLATE;
}

if ($support_deflate) {
	header("Content-Encoding: deflate");
	ob_start("compress_output_deflate");
} else{
	if($support_gzip){
		header("Content-Encoding: gzip");
		ob_start("compress_output_gzip");
	} else {
		ob_start();
	}
}
?>

В index.php сразу после
require_once("./include/function.php");

добавляем:
require_once("./include/gzip.php");

Идем на whatsmyip.org/mod_gzip_test/, вводим адрес нашего сайта — ура, все работает!

Но мы сжали лишь сами страницы. А можно сжимать еще и js с css. Для этого архивируем их в gzip и ложим (подчеркну — не удалив старые) рядом со старыми в те же папки, а в корневом .htaccess прописываем:

RewriteEngine On
AddEncoding gzip .gz
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{HTTP_USER_AGENT} !Safari
RewriteCond %{REQUEST_FILENAME}.gz -f
RewriteRule ^(.*)$ $1.gz [QSA,L]


Т.е. был файл templates/skin/new/js/panel.js, а рядом с ним получился templates/skin/new/js/panel.js.gzip У кого поддерживается gzip — теперь будет получать архив. JS можно еще сжать — см. шаг 4.

Шаг 4. Упаковка JS. На замечательном сервисе dean.edwards.name/packer/ вы можете добавить в верхнее окно исходный JS, поставить галочки base62, Shrink variables и получить по-максимуму сжатый JS-код. Экономия значительная. Единственное — не стоит упаковывать skin/templates/new/js/panel.js (почему-то не работает тогда).

Шаг 5. Настройка mod_deflate. Этот шаг нужен лишь тем, у кого есть mod_deflate, но он неправильно настроен. В корневой .htaccess прописываем:
<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript
  SetOutputFilter DEFLATE
  BrowserMatch ^Mozilla/4 gzip-only-text/html
  BrowserMatch ^Mozilla/4\.0[678] no-gzip
  BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html
  SetEnvIfNoCase Request_URI \.(?:gif|png)$ no-gzip dont-vary
  Header append Vary User-Agent env=!dont-vary
</IfModule>

Плюс см. шаг 4.

Таким образом лично я уменьшил загружаемую главную страницу с 400 Кб до 100.

PS Если у вас нет mod_deflate — можете попробовать webo.in/articles/habrahabr/87-web-optimizer-installation/ — но у меня ничего не вышло, поэтому стал использовать includes/gzip.php.

PPS В Tips&Tricks не смог запостить — видимо, кармы не хватает.

44 комментария

avatar
Спасибо большое! Полезное how-to.
avatar
Не нужно делать base62 и Shrink variables для javascript, достаточно просто сделать Pack (почистить от комментариев, пустых строк и т.д.) и gzip'ить. Тесты показывают, что закодированный js исполняется в браузере намного медленнее. Например, jQuery уже даже не поставляется в таком варианте. И, кроме того, закодированный вариант сжимается gzip даже немного хуже, чем просто почищенный. Да и «почему-то не работает тогда» тоже в таком случае возможен.
avatar
Возможно.
avatar
А что вы думаете по поводу модуля mod_deflate? Можно ли его поставить сжимать css, js, html файлы? Просто хочу поставить, но вот думаю стоит или нет!
avatar
Сжатие есть хорошая вещь. Но, с одной стороны, вы экономите свой и чужой трафик, с другой — нагружаете сервер. Всё, как всегда, зависит от задач и условий.
avatar
Да тут даже думать не стоит — однозначно полезная вещь. Сайт будет в несколько раз быстрее грузиться -> пользователи счастливы. :)
avatar
а чем в gzip ковертерить?
avatar
Если речь о css/js и упаковке вручную — то с помощью 7-zip можно.
avatar
Спасибо, думал нужно что то специальное
avatar
супер, спасибо большое
avatar
$_SERVER['HTTP_TE'];
Что это?
avatar
ниже ответил
avatar
У меня при включенном .htaccess не сжимает JS файлы! Хотя все вроде бы включенно, но не сжимает, незнаю почему!
avatar
Не работает через сжатие css и настройкой .htaccess… точнее передаются архив, но вот в заголовке нет упоминания того что это архив и FF пытается его открыть как обычный css или js… Кто-нибудь сталкивался с этим?
avatar
прописать в том же .htaccess соответствие расширению заголовка
avatar
То ли баг, то ли фича, в общем один юзер прислал такое:
Notice: Undefined index: HTTP_TE in /velorama.net/include/gzip.php on line 22

Warning: session_start() [function.session-start]: Cannot send session cookie - headers already sent by (output started at /velorama.net/include/gzip.php:22) in /velorama.net/classes/modules/sys_session/Session.class.php on line 53

Warning: session_start() [function.session-start]: Cannot send session cache limiter - headers already sent (output started at /velorama.net/include/gzip.php:22) in /velorama.net/classes/modules/sys_session/Session.class.php on line 53


Понятно, что нотисы можно отключить, что бы не мешало работе, но с этим хедером HTTP_TE что то сделать надо.
avatar
В LS 0.3.1 достаточно только gzip.php положить в include — загружается автоматом…
avatar
Теперь периодически валятся — по всей видимости что то надо поправить в Session.class.php, вопрос что?
[09-Июл-2009 06:47:05] PHP Notice: Undefined index: HTTP_TE in /home/h0sting/public_html/detsky-mir.com/include/gzip.php on line 22
[09-Июл-2009 06:47:05] PHP Warning: session_start() [function.session-start]: Cannot send session cookie — headers already sent by (output started at /home/h0sting/public_html/detsky-mir.com/include/gzip.php:22) in /home/h0sting/public_html/detsky-mir.com/classes/modules/sys_session/Session.class.php on line 53
[09-Июл-2009 06:47:05] PHP Warning: session_start() [function.session-start]: Cannot send session cache limiter — headers already sent (output started at /home/h0sting/public_html/detsky-mir.com/include/gzip.php:22) in /home/h0sting/public_html/detsky-mir.com/classes/modules/sys_session/Session.class.php on line 53
avatar
кстати не шутка, а так и есть иногда и не понятно по какой причине
avatar
Сделал как посоветовали выше. Действительно в LS 0.3.1 достаточно всего лишь закинуть gzip.php в include/
Замчательная CMS я вам скажу =)
avatar
Загрузка всего подряд по надобности и без неё — не тот случай, который заслуживает эпитета «замечательно»
avatar
Notice: Undefined index: HTTP_TE in /velorama.net/include/gzip.php on line 22

выдает такую ошибку и тест на whatsmyip.org/mod_gzip_test/ выдает, что страница не сжата…
avatar
Ну вы даёте! Не только позаимствовали идею, но и клонировали домен у velorama.ru
Завтра покажу ваш прожект разработчикам оригинала, думаю им будет забавно полистать вашу подделку.

avatar
onthefly, velorama.net гораздо раньше запустилась)))
Другое дело что конечно c velorama.ru сделали на djem'e конечно гораздо круче)
avatar
Велорама.ру ведёт свою историю с осени 2007 года, в то время как домен клона был зареган только спустя полгода.
avatar
Ну и что дальше? Сайты разные, хотя тематика одна — это что преступление?
avatar
Ладно бы просто тематика, но домен и название полностью созвучны. Это не преступление, просто недостойный поступок.
avatar
velorama.ru — это велорама, а velorama.net — это нет велорамы, значит это не есть велорама

всё логично — это два разных домена
avatar
жжошь.

Логика — весч мощная.
avatar
ЗЫ и названия разные «Велорама» и VELOrama это вообще по сути разные вещи. Велорама это рама от велосипеда (и сообщество велолюбителей), а VELOrama (заметь латиницей) — просто сообщество вело-любителей! Понимаешь, это два разных сообщества.
avatar
Может это связано с настройками сервера? Я бы на вашем месте помучал саппорт хостера на предмет наличия переменной HTTP_TE в настройках сервера.
avatar
Да опечатка это!))) Нет такой переменной=)
avatar
Хотя в гугле текст распространен.
Видимо, должна быть)
avatar
Всё верно, такой переменной нет. TE расшифровывается как transfer encoding. Это HTTP-заголовок, служащий для определения поддержки клиентом сжатия, аналог HTTP_ENCODING.

Может содержать в себе следующие признаки: deflate, gzip, chunked, identity, trailers
avatar
а в nginx-е gzip-ование отдаваемого контента включается одной единственной директивой в конфиге :-)
avatar
Да, но не у всех VDS. И тем более nginx.
avatar
некрофил! ;)
avatar
обобщила все это:
1. gzip.php — размещаем в /include/

<?php
// gzip.php v1.3 — убраны ошибки HTTP_TE, добавлено принудительное сжатие для IE
// released on 2004-05-06, by Roman Mamedov<roman at rm.pp.ru>
// license: do with this code whatever you want.

///// Configuration //////////////////
$PREFER_DEFLATE = false; // не нужно больше при желании можно поменять местами последние if elseif
$FORCE_COMPRESSION = false; // force compression even when client does not report support
$AE = 'undefined';
//////////////////////////////////////

function compress_output_gzip($output) {
return gzencode($output);
}

function compress_output_deflate($output) {
return gzdeflate($output, 9);
}

if (isset($_SERVER['HTTP_ACCEPT_ENCODING']))
$AE = $_SERVER['HTTP_ACCEPT_ENCODING'];
elseif (isset($_SERVER['HTTP_TE']))
$AE = $_SERVER['HTTP_TE'];
else
if (isset($_SERVER['HTTP_USER_AGENT']) && stristr($_SERVER['HTTP_USER_AGENT'], 'msi'))
$FORCE_COMPRESSION = TRUE;

$support_gzip = (strpos($AE, 'gzip') !== FALSE) || $FORCE_COMPRESSION;
$support_deflate = (strpos($AE, 'deflate') !== FALSE) || $FORCE_COMPRESSION;

if ($support_gzip) {
header(«Content-Encoding: gzip»);
ob_start(«compress_output_gzip»);
}
elseif ($support_deflate) {
header(«Content-Encoding: deflate»);
ob_start(«compress_output_deflate»);
}
else ob_start();

?>

2. в .htaccess добавляем
AddEncoding gzip .gz
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{HTTP_USER_AGENT} !Safari
RewriteCond %{REQUEST_FILENAME}.gz -f
RewriteRule ^(.*)$ $1.gz [QSA,L]

3. выполняем в директории LS:
for f in `find. -name *.js`; do gzip -c $f > $f.gz; done;
for f in `find. -name *.css`; do gzip -c $f > $f.gz; done;

процедуру необходимо повторять после обновления затрагивающих *.js или *.css

Теоретически проблем быть не должно.
ps: возможно в .htaccess надо добавить включение правила для IE?
avatar
avatar
Это все конечно хорошо, спасибо за доработку для LS. Такой вопрос: если использую apache+nginx+eaccelerator+memcached в nginx настроено сжатие, подскажите пожалуйста есть ли смысл использовать web-optimizer или функционал из этого топика? Как бы не получилось дважды сжимать и т.д., может это уже лишнее для VPS? Есть ли смысл? Кто знает посоветуйте пожалуйста.
avatar
если в nginx включено сжатие — то данный хак использовать не нужно!
avatar
Это насчет сжатия я понял, а как насчет web-optimizer, там ведь не только сжатие, а еще какие то фишки применяются. Кто нибудь использовал?
avatar
Зачем писать весь этот бред? делаем проще, лол =)
в index.php после
21: header('Content-Type: text/html; charset=utf-8');

пишем
22: ob_start('ob_gzhandler');


и готово. что касается самих js/css файлов.


тут тоже ничего страшного нет:
создаем файл index2.php и в нем пишем следующее:
<?php
    ob_start('ob_gzhandler');
    $file_type = substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], '?')-3, 3);
    if($file_type == 'css') $file_type = 'text/css';
    elseif($file_type == '.js') $file_type = 'text/javascript';
    else $file_type = 'text/plain';

    if(file_exists(getcwd(). $_SERVER['REQUEST_URI']))
        $output = file_get_contents(getcwd(). $_SERVER['REQUEST_URI']);
    else die(header('location: /'));

    // ну и конечно же кеш для статики
    $expires = 3600*24*30;
    header("Pragma: public");
    header("Cache-Control: maxage=".$expires);
    header('Expires: ' . gmdate('D, d M Y H:i:s', time()+$expires) . ' GMT');
    header('Content-type: '.$file_type);

    die($output); // отдаем скриптом, чтобы ob_gzhandler смог сжать выход.
сюда можно еще многое добавить, например вырезать из output пустые строки и/или перевод каретки и/или комментарии:

$output = preg_replace('#\/\*(.*)\*\/#', '', $output);
$output = preg_replace('#\/\/(.*)#', '', $output); 
в данном виде эти две строки врятли будут работать, я лишь хотел показать что я имею ввиду. просто нет времени писать сложные регулярки, кто силен тот можете отписаться

для того чтобы вся эта кострукция заработала, нам необходимо весь css, js траффик пустить через index2.php. Делается это так:
RewriteRule ^(.*)\.(css|js)$    index2.php [L]

Вставить необходимо сразу под
RewriteEngine On
и вот что у нас получилось:


есть еще один момент чтобы ускорить работу сайта. в .htaccess можно добавить
ExpiresActive On
ExpiresDefault a5356800
ExpiresByType image/x-icon image/jpeg image/gif image/png a5356800
ExpiresByType application/x-javascript a5356800
ExpiresByType text/css a5356800
ExpiresByType text/html a5356800
<FilesMatch "\.(pl|php|cgi|spl|scgi|fcgi)$">
   ExpiresActive Off
</FilesMatch>

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

p.s. и, уважаемые программисты, не ставьте знак ?> в конце файла, это сэкономит не только размеры файлов, но и многие часы дебага

p.s.s в данный момент подготавливаю большой модуль «галерея изображений» ждите.
avatar
Главная фишка моего подхода заключается в том, что у меня страницы грузятся если человек находится за проксей, а представленное решение топикстартера не позволяет этого сделать ссылаясь на HTTP_TE какое то…

результат работы строки
22: ob_start('ob_gzhandler');




Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.