Клиентская оптимизация

индекс
163,90

Оптимизация загрузки страниц на практике

Наткнулся на кучу интересных статей на webo.in и зачитался. Решил применить описанное там на реальном проекте. Вот что получилось. Проектик маленький — сайт моих друзей Bookcare. Они делают обложки для книг, а их сайт — мой «проект выходного дня».
Теперь по сути:
Основная часть контента — это изображения обложек, которые хранятся в формате jpeg. Значит действия над этой частью оптимизации сводятся к оптимизации самих картинок и настройке кеширования.
Для оптимизации картинок я использовал jpegtran, однако проблема с ним была в том, что он не может обработать сразу всю папку — только по одному файлу. Другая проблема, что использовать входной файл в качестве выходного (просто перезаписать оптимизированный файл в старый) невозможно.
Я решил эту проблему небольшим скриптиком и, хотя я не очень силён в шелл-скриптах, он работает и достаточно удобно производить замены. Итак, я создал в домашней папке скрипт jp.sh:

jpegtran -copy none -optimize -perfect "$1" > "оsh_conv_$1";\
mv "$1" raw/
mv "оsh_conv_$1" "$1"

А затем выполнял простую gоследовательность:
# mkdir raw
# find *.jpg -depth 0 -type f -iregex ".*\.jpg" -exec ~/s.sh {} \;

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

Итак, после того, как картинки удобным образом оптимизированы, надо было настроить кеширование. Я добавил выдачу Etag'ов. Это просто и описано на webo.in. Делается добавлением одной строки в htaccess

FileETag MTime Size

Вообще-то надо еще настроить выдачу заголовков Expires, но это тоже делается несложно.

После разборок с каринками, они стали весить на 34% меньше, что на странице Каталога ускорило загрузку почти на столько же, так как основной контент — это именно картинки.

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

Далее интересней. Захотелось, чтобы все (даже пхпшные) странички выдавали LastModified и правильно откликались на If-Modified-Since.
Взглянул на главную страницу и понял, что там выводятся только новости и последние поступления (из динамического контента). Соответственно получил дату последней новости, дату последнего поступления, получил максимальную и обработал следующим образом:

// $md - Штамп даты последнего изменения
$modified = gmdate("D, d M Y H:i:s",$md)." GMT";// Приводим к HTTPшному формату - "Mon, 20 Dec 2004 09:34:19 GMT";
$hdr = '';

$headers = apache_request_headers();

foreach ($headers as $header => $value) {
if (eregi('If-Modified-Since', $header)) {$hdr = $value;}
}

if ($hdr === $modified) {
header ("HTTP/1.1 304 Not Modified ");
header ("Last-Modified: ". $modified);
header ("Expires:");
header ("Cache-Control:");
exit();
}
header ("Last-Modified: $modified");
header ("Expires:"); // обнуляем ненужные заголовки
header ("Cache-Control:");


* This source code was highlighted with Source Code Highlighter.


Надо отметить, что время в Last-Modified надо указывать всегда в GMT, то есть по гринвичу.

Теперь страничка загружается один раз, а далее мы видим ответ 304 Not Modified;
Еще одно важное замечание — заголовок Last-Modified надо выдавать всегда, иначе страница будет загружаться через раз, так как в ответе 304 не было указано Last-Modified. Такое поведение было замечено за FF3.

Затем добрался до выдачи css в виде архивов. Тут меня не устроило решение, которое было дано на webo.in, так как на серваке bookcare.ru нет mod_headers. Я использовал достаточно спорный метод, но в рамках данной задачи, я считаю, вполне законный:

RewriteEngine On
AddEncoding gzip .css
RewriteCond %{HTTP:Accept-encoding} !gzip [OR]
RewriteCond %{HTTP_USER_AGENT} Konqueror
RewriteRule ^(.*)\.css$ $1_css.nozip [L]

Соответственно, например main.css у нас изначально лежит в виде архива, а main_css.nozip — это обычная версия файла.
Если эти строки лежат в .htaccess в отдельной папке, то никак не влияют на файлы вне этой папки, так что в таком виде этот приём только полезен. При минимальных действиях на сервере получается то, что должно получаться.

Вот, наверное всё, если я чего-нибудь не запамятовал.

UPD1: Всё-таки запамятовал. После вырезания всех лишних символов из хтмл-выдачи отдаётся она в gzipованом виде с коэффициентом сжатия 9.

UPD2: Переписал шелловый скрипт. Теперь всё работает хорошо и с любыми именами

UPD3: Обнаружилась проблема с выдачей css в виде архивов. Исправил код .htaccess

UPD4: По совету юзера Roxis публикую код на пхп, который выбаёт правильные заголовки без привязки к mod_php

$hdr = isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])?$_SERVER['HTTP_IF_MODIFIED_SINCE']:'' ;

if ($hdr === $modified) {
 header ("HTTP/1.1 304 Not Modified ");
 header ("Last-Modified: $modified");
 header ("Expires:");
 header ("Cache-Control:");
 exit();
}
header ("Last-Modified: $modified");
header ("Expires:");
header ("Cache-Control:");


* This source code was highlighted with Source Code Highlighter.


P.S. Прошу сильно не пинать, это мои первые шаги по мосту клиентской оптимизации. Но за замечания буду только благодарен.
+34
3 ноября 2008, 17:46
65

комментарии (46)

0
xzander #
а можно кат?
+3
coylOne #
дадада. простите великодушно =)
0
sunburn #
Того, кто просит убрать под кат всегда минусуют. Но кто то же должен подсказать это автору? Зачем минусовать.
0
DibrovLALALA #
а ещё обычно есть человек, который говорит, что «того, кто просит убрать под кат всегда минусуют»

будем продолжать цепочку или уже наконец-то сделают автокат?
+5
enartemy #
A.
$m = explode(' ', $d2);// $d2 - дата в формате MySQL - "YYYY-mm-dd hh:mm:ss"

$m['d'] = explode('-',$m[0]);// Разбиваем, чтобы потом получить штамп
$m['t'] = explode(':',$m[1]);

$md = mktime($m['t'][0],$m['t'][1],$m['t'][2],$m['d'][1],$m['d'][2],$m['d'][0]);


Вот так делать — это глупость.

1. Не будущее — лучше даже в mysql-ой базе хранить данные в виде unix timestamp. Т.е. обычным числом, тогда не придется так изворачиваться.
2. Даже если вы храните в дате, в выборке можно спокойно указать SELECT UNIX_TIMESTAMP(date); и получить на выходе готовый.
3. Даже если приходится хочешь-нехочешь а обрабатывать время в виде mysql-формате, то для этого существует php-функция strtotime.
4. А вообще, если делать по нормальному надо прямо в SELECT-формировать дату в нужном формате. Это спокойно делается, посмотрите ф-ии mysql для работы со временем.

Б. Даже в HTML выдаче лчше делать на основе E-tag, который может вычисляться как md5 или sha1 от времени последнего изменения страницы. Результат проще, но мороки меньше.

В. О eregi уже давно пора немного забыть.
+3
coylOne #
Согласен со всем. Поправлю.
+1
sunnybear #
1. последний вариант не будет работать корректно без mod_headers, лучше уже тогда
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE application/x-javascript
— сам недавно так на одном виртуальном хостинге подрубал

либо так, как описано в самом начале неуказанной статью по сжатие:
AddEncoding gzip .gz
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}.gz -f
RewriteRule ^(.*)$ $1.gz [QSA,L]

2. Про gzip и альтернативные хосты ни слова — они применялись? Хотя, на странице, в среднем, не больше 5 картинок, так что особо и не требуется…

3. Да, и можно эту статью на webo.in перепечатать? :)
+1
coylOne #
1. Ну я не указал конкретно статью, просто сослася на сам сайт =)

2. Альтернативные хосты не применял, но, собираюсь, ибо сейчас сайт в зачаточном состоянии, а вообще — там основная задача — показывать товар лицом. например страница Каталога вообще только и содержит, что картинки/

3. почту за честь.
0
sunnybear #
про gzip не ответили :) Приведите его тоже в статье, так как для HTML он точно как-то включен :)
0
coylOne #
done
+1
VSOP_juDGe #
А результат-то такой? Какое было время загрузки, на сколько уменьшилось? Судя по сайту, он и неоптимизированный вряд ли тормозил – стоила ли игра свеч?
+1
coylOne #
Картинки стали весить меньше на 30%. На странице каталога это было очень критично, да и кеширование свело время загрузки (для не новых пользователей) на нет.
+1
coylOne #
А Last-Modified помогает не только юзерам, но и поисковым роботам.
0
t0os #
Ну а в среднем загрузка страницы насколько стала быстрее? Замеры проверяли?
+1
Roxis #
без привязки к mod_php: $_SERVER['HTTP_IF_MODIFIED_SINCE']
0
coylOne #
спасибо
0
coylOne #
Добавил исправленный с Вашей помощью код =)
0
Roxis #
ещё проверку isset() желательно добавить
0
coylOne #
done
+1
ssve #
"… тдаётся она в gzipованом виде с коэффициентом сжатия 9.."
Сожмет чуть лучше чем на 1...3, а процессора поест в разы больше

0
coylOne #
Этот вопрос я еще не изучал пристально. В ближайшее время поиграюсь разными значениями, потестирую. Если получится что-то дельное — напишу.
0
ssve #
Народ давно уже поигрался и графики нарисовал, ищите в ПС
0
coylOne #
Эти тесты — вещь сильно зависящая от среды, так что новые графики для сравнения будут не лишними.
+1
egorinsk #
Насчет файлов с кириллицей — это проблемы с начтройкой сервера, такого не должно быть. И в баш-скрипте *всегда* берите имена файлов в кавычки, "$1" вместо $1, иначе будут глюки с файлами с пробелами и спецсимволами.
0
coylOne #
Спасибо, буду иметь в виду. Видимо из-за этого и были проблемы. Сейчас проверю.
+1
FrankSinatra #
для оптимизации и чистки html кода могу посоветовать использовать tidy, www.w3.org/People/Raggett/tidy/
0
coylOne #
Ну тут не совсем такая оптимизация нужна. Например, в одном из методов уменьшения размера html-кода предлагается не закрывать некоторые теги. Тут же просто приводится к «идеальному» виду, например:
heading
<h2>subheading

is mapped to

heading
subheading

Тут размер увеличивается.
0
coylOne #
<h1>heading
<h2>subheading</h3>

is mapped to

<h1>heading</h1>
<h2>subheading</h2>

Простите, теги не поправил
0
FrankSinatra #
тиди не только это умеет делать, не внимательно читали
0
coylOne #
это точно =)
+2
nini #
А я думал, че за глюки в ФФ3 иногда…
+1
CAJAX #
И на сколько сократился трафик? Какие результаты в целом? Или пока рановато мерить?
+2
kolupaeva #
Спасибо, интересно.

Опечатка в третьей строке примера ($hrd=''), было бы хорошо исправить на случай копи-паста.

— BookCare — это отлично! Отчаянно хочу обложку :)
–5
phpdude #
микрооптимизация сайтов визиток — конечно ахуенная тема. бред. ТС найди норм работу раз умный
–1
alvares #
Ошибаетесь милейший, это не микрооптимизация сайтов визиток, а привлечение траффика на говносайт.
–1
phpdude #
с этим я полностью согласен. статья на заказ
0
coylOne #
> с этим я полностью согласен
Ваше право
0
coylOne #
> привлечение траффика
Вот это вообще ноу комментс. Айтишники с электорнными книгами на своих сони ридерах — самая целевая аудитория для производителей обложек книг.

> на говносайт
ХСПВ, милейший.
0
coylOne #
> микрооптимизация сайтов визиток — конечно ахуенная тема. бред.
Ну уж простите, не у каждого есть под рукой яндекс. За неимением большего, попробовал воплотить методы на малом сайте.
Представленные методы без труда можно применить и на большом портале.

> ТС найди норм работу раз умный
Позвольте мне решать самому, что делать.
0
xintrea #
> Проектик маленький — сайт моих друзей Bookcare.

Да что ж такое… Проектик маленький, а раздел «Зачем мне обложка?» существует только в виде пункта меню.
0
coylOne #
Спасибо за сигнал, товарешч =)
+1
mrShadow #
# find *.jpg -depth 0 -type f -iregex ".*\.jpg" -exec ~/s.sh {} \;

Есть способ проще:

# for i in *.jpg; do ~/s.sh "${i}"; done
0
coylOne #
Спасибо, учту.
0
Rikunov #
>>>… иначе страница будет загружаться через раз, так как в ответе 304 не было указано Last-Modified. Такое поведение было замечено за FF3.

Я столкнулся с подобной проблемой. Но у меня это отражается на картинках. Вкратце, я делаю поддержку тем для веб-приложения. Чтобы иметь возможность переключать тему «на лету» я добавил опции кэширования. Пока тема прежняя сервер для всех картинок и css отвечает 304, как только тема меняется, серве отвечает 200 меняя last modified timestamp и etag. Плюс я использую «Cache-Control»: «public, must-revalidate». Работает в Chrome, IE, Safari. В FF 3 работает «частично». Проблема в следующем. В приложении есть Табы, которые состоят из 3-х картинок — левая часть, правая часть, бэкграунд. Так вот Табов больше одного, соответственно одинаковых ссылок на одни и те же картинки столько же сколько и Табов. Так вот FF 3 обновляет картинки Табов не для всех Табов, причем LiveHTTPHeaders и Fiddler показывают что сервер работает правильно — отдает «свежую» картинку. Но FF 3 обновляет не все Табы :(( Такое ощущение что есть проблема в синхронизации. Например, FF одновременно шлет несколько запросов на одну и ту же картинку. Первый реквест получает 200 респонс. Следующий реквет уже получает 304 респонс. Первая картинка обновляется из респонса и кладется в локальны или ин-мемори кэш FF, но при этом успевает вернуться второй ответ с кодом 304, который лезет в кэш и берет еще не обновившуюся картинку %). Если кликнуть по любой ссылке или рефрешнуть страничку, то все обновится правильно, потому что в кэше уже будут лежать «свежие» картинки.

Помогите кто может советом, очень хочется побороть эту траблу?
0
coylOne #
Ну можно попробовать картинки не в верстке прописывать, а в css. Тогда загрузка должна быть одна (для одного свойства).
0
Rikunov #
Что я хочу понять — это баг в FF или есть решение? :) Мне кажется, что Вы правы насчет CSS — в крайнем случае сделаю именно так. Спасибо!

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