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

    Наткнулся на кучу интересных статей на 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. Прошу сильно не пинать, это мои первые шаги по мосту клиентской оптимизации. Но за замечания буду только благодарен.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Комментарии 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 он точно как-то включен :)
          • +1
            А результат-то такой? Какое было время загрузки, на сколько уменьшилось? Судя по сайту, он и неоптимизированный вряд ли тормозил – стоила ли игра свеч?
            • +1
              Картинки стали весить меньше на 30%. На странице каталога это было очень критично, да и кеширование свело время загрузки (для не новых пользователей) на нет.
              • +1
                А Last-Modified помогает не только юзерам, но и поисковым роботам.
              • 0
                Ну а в среднем загрузка страницы насколько стала быстрее? Замеры проверяли?
                • +1
                  без привязки к mod_php: $_SERVER['HTTP_IF_MODIFIED_SINCE']
                  • 0
                    спасибо
                    • 0
                      Добавил исправленный с Вашей помощью код =)
                      • 0
                        ещё проверку isset() желательно добавить
                  • +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
                              тиди не только это умеет делать, не внимательно читали
                      • +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
                                      >>>… иначе страница будет загружаться через раз, так как в ответе 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 — в крайнем случае сделаю именно так. Спасибо!

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