Пользователь
0,0
рейтинг
3 ноября 2008 в 17:46

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

Наткнулся на кучу интересных статей на 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. Прошу сильно не пинать, это мои первые шаги по мосту клиентской оптимизации. Но за замечания буду только благодарен.
Василий Кулаков @coylOne
карма
12,1
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

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

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

        будем продолжать цепочку или уже наконец-то сделают автокат?
  • +5
    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
      Согласен со всем. Поправлю.
  • +1
    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
      1. Ну я не указал конкретно статью, просто сослася на сам сайт =)

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

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

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

      is mapped to

      heading
      subheading

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

        is mapped to

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

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

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

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

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

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

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

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

    # for i in *.jpg; do ~/s.sh "${i}"; done
    • 0
      Спасибо, учту.
  • 0
    >>>… иначе страница будет загружаться через раз, так как в ответе 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
      Ну можно попробовать картинки не в верстке прописывать, а в css. Тогда загрузка должна быть одна (для одного свойства).
      • 0
        Что я хочу понять — это баг в FF или есть решение? :) Мне кажется, что Вы правы насчет CSS — в крайнем случае сделаю именно так. Спасибо!

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