Как заставить Apache работать или как я реализую создание миниатюр изображений в своих проектах

Здравствуйте, хабровчане.

Занимаясь созданием различных интернет-проектов уже порядочное время, часто был просто возмущён одним фактом — не все элементы системы несут на себе равную нагрузку. Я всегда придерживался мнения, что каждый элемент системы, будь то интерпретатор того или иного языка для сайта, или база данных, или даже сам HTTP сервер, должен брать на себя максимально возможную нагрузку, дабы облегчить участь остальных элементов системы. Да и сами приложения для своих проектов всегда старался максимально разгрузить.

Сейчас же мне хотелось бы поделиться своим опытом в том, как «озадачить» Apache при создании миниатюр изображений (Thumbnails).

При «переделках» сайтов с различными вариантами миниатюризации изображений долгое время сталкивался и всегда очень разочаровывался в их устройстве. Практически все решения заключались в работе скриптов и уже скрипты искали исходники для миниатюризации и генерировали заголовки, если таковое не найдено. А когда же я увидел реализацию миниатюр в CMS MODX через phpThumbOf, то невольно даже прослезился.

Так уж случилось, что пришлось делать интернет- магазин на выше упомянутой CMS. Задачка, если честно, не из простых, но самым щекотливым моментом стала генерация миниатюр. Товары и изображения к ним выгружаются из 1С и ни о какой оптимизации и стандартизации в отношении изображений, как вы, наверное, уже понимаете, и речи быть не может. В этом плане в большинстве случаев 1С можно назвать одним словом — «помойка». Каждый пихает туда как может и тянет откуда получается. Так или иначе, изображения товаров есть и их много. Также помимо изображений товаров должна быть уйма других — фото под различные акции, мероприятия… Да что уж говорить — должно быть много изображений.

Поскольку перспектива использования системной phpThumbOf меня не прельщала, так как генерация миниатюр во время генерации страницы — слишком уж дико, да и чистить миниатюры при очередном обмене данными с 1С становилось бы проблемой, я начал поиск решения, соответствующего моему концепту «работать должны все».

Суть идеи была проста:

  • вынести миниатюризатор из CMS, дабы не перегружать интерпретатор лишним и зачастую ненужным кодом;
  • иметь в минаитюризаторе не одно, а множество правил преобразования, и подгружать их по маске;
  • не использовать миниатюризатор вхолостую, т.е. не пытаться определить в нём: есть ли миниатюра или ее исходник, или нет;
  • переложить часть работы миниатюризатора на сервер Apache через .htaccess;
  • и сделать чтобы миниатюры создавались по первому требованию, а не принудительно всегда.

Реализацию самого миниатюризатора на PHP я пропущу, т.к. не о нем речь, а вот самим концептом поделюсь. Думаю, пригодится он многим.

Итак, шаг первый: хранение изображений и миниатюр при наличии множества правил преобразования исходников.

У нас есть папка на сайте, в которой лежат все изображения. Пусть это будет папка «images». В этой папке есть множество подкаталогов с картинками на различные тематики.

Создадим в ней подкаталог для хранения миниатюр. Пусть это будет каталог «thumb».

Внутри этого каталога будут лежать подкаталоги с миниатюрами. Каждый подкаталог соответствует определённому правилу преобразования исходного изображения. Пусть это будут подкаталоги «a,b,c,d». В каждом из них будут лежать изображения из каталога «images» и ниже, пропущенные через миниатюризатор с использованием общего правила преобразования.

Таким образом, для изображения «/images/folder1/image1.jpg» после преобразования по шаблону «a» будет миниатюра «/images/thumb/a/folder1/image1.jpg».

Шаг второй: добавляем сам миниатюризатор.

Я просто кладу скрипт с вызовом миниатюризатора в папку изображений. Пусть это будет «/images/thumb.php». Хотя никто не мешает поместить его где угодно. Главное не забыть отразить это в «.htaccess» в RewriteBase и RewriteRule.

И в конце: даём пинок серверу Apache.

Создаем файлик «/images/.htaccess» со следующим содержанием:

RewriteEngine On
RewriteBase /images/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} ^/([^/]+)/thumb/([^/]+)(/.+)?/(.+)\.(jpe?g|png|gif|svgz?|tiff?)$
RewriteCond %{DOCUMENT_ROOT}/%1%3/%4.%5 –f
RewriteRule ^(.*)$ thumb.php [L,QSA]

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

Сервер сперва проверит есть ли вообще запрашиваемое изображение. Если такого нет, то проверит, является ли это запросом миниатюры. Если это должна быть миниатюра, то он должен проверить есть ли исходник для запрашиваемой миниатюры. Если исходник существует, то он запускает миниатюризатор.

Ложь на любом этапе проверки запускает стандартную логику сервера. Т.е. если запрашиваемое изображение есть – он сам его отдает; если это не запрос миниатюры или нет исходника для запрашиваемой миниатюры, то Apache вернёт статус 404 «не найдено».

Таким образом, мы реализовали доступ к миниатюрам и к скрипту для их создания, причем без лишнего вызова такового. Также такая система хранения снимает неудобства связанные с прочисткой кэша миниатюр.

А теперь усложним задачу для сервера.

Допустим, у меня есть SVG изображение «/images/svgs/logo.svg» а в кэше хочу иметь миниатюру в формате PNG.
По выше указанной схеме Apache просто не найдёт исходника для «/images/thumb/b/svgs/logo.png», т.к. сервер ищет исходник с тем же расширением.

Можно заставить Apache подумать побольше:

RewriteEngine On
RewriteBase /images/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} ^/([^/]+)/thumb/([^/]+)(/.+)?/(.+)\.(jpe?g|png|gif|svgz?|tiff?)$
RewriteCond %{DOCUMENT_ROOT}/%1%3/%4.jpg -f [OR]
RewriteCond %{DOCUMENT_ROOT}/%1%3/%4.jpeg -f [OR]
RewriteCond %{DOCUMENT_ROOT}/%1%3/%4.tif -f [OR]
RewriteCond %{DOCUMENT_ROOT}/%1%3/%4.tiff -f [OR]
RewriteCond %{DOCUMENT_ROOT}/%1%3/%4.gif -f [OR]
RewriteCond %{DOCUMENT_ROOT}/%1%3/%4.bmp -f [OR]
RewriteCond %{DOCUMENT_ROOT}/%1%3/%4.png -f [OR]
RewriteCond %{DOCUMENT_ROOT}/%1%3/%4.svg -f [OR]
RewriteCond %{DOCUMENT_ROOT}/%1%3/%4.svgz -f
RewriteRule ^(.*)$ thumb.php [L,QSA]

Теперь, прежде чем отправить наш запрос миниатюры скрипту, Apache попробует найти исходник для запрашиваемой миниатюры в другом формате. Он просто переберёт все возможные расширения для этого файла и если не нашел соответствия опять же вернёт нам 404 «не найдено».

Теперь при запуске миниатюризатора нам достаточно лишь проверить наличие переменной «REDIRECT_URL», чтобы удостовериться, что это редирект, а не прямой запуск скрипта. В остальном миниатюризатор уже точно знает, что исходник есть. Да и куда положить результат своей работы он уже знает, и откуда взять исходник. Все в одной и той же переменной окружения «REDIRECT_URL».

Обнулять кэш очень легко – указываем папку исходников, для которой нужно очистить кэш, программа подгрузит наименования шаблонов и прочистит кэш для каждого шаблона преобразований. Также можно прочистить только для одного шаблона. А для обновления кэша при обмене с 1С просто проверяем контрольные суммы имеющегося изображения и входящего, и при несоответствии заменяем исходник и вычищаем все его миниатюры.
Поделиться публикацией

Похожие публикации

Комментарии 19

    0
    Идея интересная, спасибо. В перспективе не думаете переложить отдачу статики и подобные проверки на nginx?
      0
      Возможно, но позже. Опыт работы с Nginx пока скудный — только на уровне допила ISPmanager.
        0
        «В перспективе не думаете переложить отдачу статики и подобные проверки на nginx?»
        У нас используется похожая схема, и в ней есть nginx.
        В нашей схеме наличие сгенерированного файла проверяет сначала nginx и если нужная картинка уже есть — отдаёт её своими силами.
        Если же файл не найден — происходит передача запроса Апачу, а далее всё по похожей схеме как в данной статье
        0
        Я описанный вами первый метод уже несколько лет использую в работе. К стати, узнал я о нем на Хабре, так что тема, как говорится, паленая :)
          0
          Рад за вас. Метод простой, но действенный. И уж 100% я не первый. Просто я перерыл google в поисках чего-то подобного но не нашел (возможно потому, что с google общаюсь исключительно на английском). На самом деле у меня есть и чуть более сложный вариант с использованием setenv но я не стал его сюда публиковать, т.к. данный модуль не везде включён. В нем интерпретатору передаются оба маршрута и шаблон преобразования через окружение. А одна из задач поста немного переосмыслить такую элементарную штуку как htaccess и mod_rewrite. Ведь представленные на просторах интернета рецепты напоминают фрагмент монолога Р. Карцева «Отец»: "… Папаня, на уникальных японских электронных микроскопах пальто висят. — Ну вам наша вешалка?.."
          –1
          Бандл для Symfony2 с описанным подходом: github.com/avalanche123/AvalancheImagineBundle
            +1
            По такой же логике можно сделать склеивание/минимизацию css и js
              +1
              Склеивание и минимизация должны выполняться перед или во время деплоя.
                +1
                Смотря что и как склеивать. Сомневаюсь что у кого-нибудь найдется пара тысяч css или js файлов для регулярного сжатия или склеивания… если это конечно не репозиторий сборных библиотек. А вот даже в том же магазине уже более 30 000 изображений только в каталоге товаров. И вопрос оптимизации и удобства в управлении — №1.
                  0
                  Да без разницы, два файла там или тысяча, если это статика. Один раз обработал и забыл. Без всяких извращений с апачем.
              +1
              1.
              А почему не положить thumb.php в папку /images/thumb/? остальные файлы будут работать как и раньше. Ведь проблем с отдачей оригинальных картинок нет, значит надо бить именно по миниатюркам. Так бьете по воробьям из пушки.

              2.
              Когда работаем только с миниатюрками, зачем web-серверу искать несколько файлов, получается по вашему конфигу, на один первый запрос(когда нет миниатюрки) — 6 обращений к диску. Думаю можно просто посмотреть есть файл или нет, если нет отправить на thumb.php, а вот он уже пусть сделает svg -> png. В php через функцию glob() можно по маске найти файл(ы), а там уже легко понять что хотят от нас. К тому же решается проблема, если завтра кто-то захочет jpg, а не png. По вашему конфигу web-сервер будет отдавать ту миниатюру, которую сделали первую. А так php может ложить картинки в том же расширении что и запросили.
                0
                1.
                .htaccess тоже идет в /images/thumb/
                  0
                  по-хорошему из папки /images/ ничего выполняться не должно, скрипты должны лежать отдельно от директорий загрузки
                    0
                    Да, действительно, *.php хорошо бы вообще убрать отсюда и нормально настроить .htaccess только на отдачу файлов, просто тут обсуждали «нагрузку». И я просто написал свой опыт, что лучше работать только с миниатюрками, а не со всеми картинками.
                    Так получается, если была бы папка /files/images/, то .htaccess лежал бы в /files? с дополнительной проверкой картинка это или нет, на примере проверки миниатюрка или нет.
                    0
                    1.
                    А зачем класть сам скрипт? Достаточно разместить там сам .htaccess с соответствующими изменениями. Скрипт может лежать в любом месте в пределах публичной папки чтобы Apache мог туда перенаправить. Если разместить вне DOCUMENT_ROOT или в директории с ограничением доступа доступа к скрипту не будет.

                    2.
                    Так суть как раз в том чтобы не дёргать скрипт по поводу и без. Apache согласно набору правил сам определяет целесообразность запуска скрипта проверяя наличие исходника для преобразования. А сохранять можно хоть в pdf. Если конечно очень хочется. И будет лежать 1.jpg 1.png 1.gif и т.д. в папке /images/thumb/a.

                    И еще… даже проверять на редирект в скрипте не обязательно. Кидаем в QUERY_STRING ключик и добавляем проверочку.

                    RewriteCond %{REQUEST_FILENAME} !-f
                    RewriteCond %{REQUEST_URI} ^/([^/]+)/thumb/([^/]+)(/.+)?/(.+)\.(jpe?g|png|gif|svgz?|tiff?)$
                    RewriteCond %{DOCUMENT_ROOT}/%1%3/%4.%5 –f
                    RewriteRule ^(.*)$ thumb.php?cs=eaf59842b9a337232f32b05d5b1f0c19 [L,QSA]
                    
                    RewriteCond %{REQUEST_FILENAME} thumb\.php
                    RewriteCond %{QUERY_STRING} !^cs=eaf59842b9a337232f32b05d5b1f0c19$
                    RewriteRule . - [F,L]
                    

                    А при хранении шаблонов преобразований в файлах, можно даже застраховаться от вызова миниатюризатора с неверным шаблоном. И все это без запуска скрипта.
                      0
                      1. Да, я тут немного запутался, я про .htaccess, скрипт отдельно должен лежать. Выше писали.

                      2. Я просто не люблю логику в разные места закладывать, стараюсь чтоб все в одном месте было. Так у вас не будет разницы какой web-сервер стоит. Nginx, apache и т.д. И другой программист скажет спасибо; Допустим я на локальной машине использую связку nginx + php-fpm, ваш проект не так просто будет поднять, весь .htaccess я должен переписать под nginx. В моем варианте любой web-сервер должен сделать только одно, если нет файла, то спроси у приложения. Завтра вы по разному URL'у сможете делать разные миниатюрки, с белым фоном/прозрачным, 120x90px, 600x800px, gif/png/jpeg и т.д. И не надо лезть и вспоминать как писать правила для apache/nginx, достаточно будет настроить php. В основном вы же пишите на php?
                      Как примеры
                      /images/a/b/filename.jpg — оригинал
                      /images/thumb/a/b/filename.jpg — стандартная миниатюрка
                      /images/thumb/a/b/filename.jpg.png — стандартный размер, но в png
                      /images/thumb/a/b/filename.jpg_120x90.jpg — размер 120x90
                      /images/thumb/a/b/filename.jpg_600x800.png — размер 600x800

                      Все это сможет сделать php, главное чтоб ему web-сервер не мешал.
                        0
                        Речь то не о PHP. Если не обратили внимание, речь идет о распределении нагрузки. Работа любого интерпретатора изначально более дорогая (создание окружения, подгрузка кода и т.д.) чем минимальная логика на уровне HTTP сервера. А для несчастных владельцев CGI особенно, поскольку интерпретатор при использовании того же PHP в качестве модуля или FCGI уже запущен и ждёт команды, а при CGI для начала работы приложения требуется больше времени. При этом у нас всегда есть сам сервер. И если есть возможность за счёт его способностей снять лишнюю нагрузку с интерпретатора — грех этим не воспользоваться. В данном случае приводится лишь пример как не запускать скрипт впустую благодаря mod_rewrite.
                        И вообще я сталкивался столько с перенаправлениями типа:
                        RewriteCond %{REQUEST_URI} thumb
                        RewriteRule ^(.*)$ thumb.php [L]
                        

                        или
                        <FILES thumb>
                        # давай работай что-нибудь
                        </FILES>
                        

                        что аж страшно… А потом владельцы сайтов начинают верещать «Ой мне письмо пришло от хостера, что у меня превышение процессорного времени и меня отключат». А что удивляться? Времена меняются. Если лет 6-7 назад подсчёт процессорного времени был в диковинку, то сейчас почти на каждом шагу. На одном сайте даже слайдер починял — изображения 1200х450 генерировались при каждом заходе на страницу.
                          0
                          Но я ведь не говорю, что web-сервер не должен проверять есть файл или нет и каждый раз нарезал новую картинку. Вот если бы web-сервер еще сам нарезал, тогда я бы понял. Так кстати умеет nginx с модулем «http_image_filter_module». Или лучше отправлял на другое приложение (утилиту), которое нарезает быстрее и меньше памяти использует. Но это все оптимизация на уровне очень высоконагруженных проектов, не думаю что каждый проект такой нагруженный, другое дело что завтра с этим кодом кто-то будет работать. Часы программисты сейчас дороже. Поэтому приложения должны быть маскимально простыми и понятными.

                          Думаю у нас спор на пустом месте. Разница в нагрузке тут минимальная, так как и в Вашем и в моем варианте картинка нарезается один раз. Я просто предложил более гибкий вариант, в котором web-сервер не мешает.
                            0
                            В том то и дело что спор на пустом месте. Ведь и ваш пример можно посадить на те же салазки. И поверьте, сервер не будет мешать а только помогать. По первому примеру и с таким вот регулярником например.
                            ^/([^/]+)/thumb/(.+)?/([^\.]+)\.(jpe?g|png|gif|svgz?|tiff?)(.+)?$
                            

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

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

                  Самое читаемое