Статьи

Сокращение HTTP-запросов с помощью сгенерированных URI данных

Я на самом деле не серверный парень, но я балуюсь тут и там, а на днях у меня появилась эта действительно классная идея, которой я хотел бы поделиться. Конечно, это может быть опытным программистом PHP ! Но тогда я надеюсь, что вы будете заинтересованы в моей реализации, и кто знает — может быть, я собираюсь сделать чей-то день!

Идея заключается в следующем: вы можете уменьшить количество HTTP- запросов, которые страница должна сделать для своих изображений, предварительно обработав исходный код и преобразовав их в URI data . Фактически, до тех пор, пока общий объем задействованных данных не угрожает ограничению памяти PHP , вы можете уменьшить число до нуля!

Схема URI данных — это средство включения данных в веб-страницы, как если бы они были внешним ресурсом. Он может использоваться для любых данных, включая изображения, скрипты и таблицы стилей, и поддерживается во всех современных браузерах: браузерах Gecko, таких как Firefox и Camino; Браузеры Webkit, такие как Safari, Konqueror и Chrome; Опера, конечно; и IE8 ограниченным образом (но не IE7 или ранее).

Однако, как Google вскоре подтвердил, у меня не первая идея использовать их для оптимизации страниц. Но реализации, которые я видел, вращались вокруг переписывания путей к изображениям вручную, чтобы указать им на сценарии, что-то вроде этого:

 <img src="<?php echo data_uri('images/darwinfish.png'); ?>" alt="Darwin Fish" /> 

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

Код — это то, где сердце

В следующем примере представлена ​​полная демонстрационная страница с оригинальным HTML и CSS , окруженная PHP .

Страница содержит пять элементов <img> и одно CSS- background-image , но в поддерживаемых браузерах она вообще не делает никаких дополнительных HTTP- запросов :

 <?php if($datauri_supported = preg_match("/(Opera|Gecko|MSIE 8)/", $_SERVER['HTTP_USER_AGENT'])) { ob_start(); } ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> <title>Data URI Generator</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <style type="text/css"> body { background:url(images/texture.jpeg) #e2e2dc repeat; color:#554; } </style> </head> <body> <p> <img src="images/dropcap.jpg" alt="dropcap.jpg" /> <img src="images/firefox.png" alt="firefox.png" /> <img src='images/specificity.jpg' alt='specificity.jpg' /> <img src='images/darwinfish.png' alt='darwinfish.png' /> <img src="images/rolleyes.gif" alt="rolleyes.gif" /> </p> </body> </html> <?php if($datauri_supported) { function create_data_uri($matches) { $filetype = explode('.', $matches[2]); $filetype = strtolower($filetype[count($filetype) - 1]); if(!preg_match('/^(gif|png|jp[e]?g|bmp)$/i', $filetype)) { return $matches[0]; } if(preg_match('/^//', $matches[2])) { $matches[2] = $_SERVER['DOCUMENT_ROOT'] . $matches[2]; } @$data = base64_encode(file_get_contents($matches[2])); return $matches[1] . "data:image/$filetype;base64,$data" . $matches[3]; } $html = ob_get_contents(); ob_end_clean(); $html = preg_split("/r?n|r/", $html); while(count($html) > 0) { $html[0] = preg_replace_callback("/(src=["'])([^"']+)(["'])/", 'create_data_uri', $html[0]); $html[0] = preg_replace_callback("/(url(['"]?)([^"')]+)(["']?))/", 'create_data_uri', $html[0]); echo $html[0] . "rn"; array_shift($html); } } ?> - <?php if($datauri_supported = preg_match("/(Opera|Gecko|MSIE 8)/", $_SERVER['HTTP_USER_AGENT'])) { ob_start(); } ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> <title>Data URI Generator</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <style type="text/css"> body { background:url(images/texture.jpeg) #e2e2dc repeat; color:#554; } </style> </head> <body> <p> <img src="images/dropcap.jpg" alt="dropcap.jpg" /> <img src="images/firefox.png" alt="firefox.png" /> <img src='images/specificity.jpg' alt='specificity.jpg' /> <img src='images/darwinfish.png' alt='darwinfish.png' /> <img src="images/rolleyes.gif" alt="rolleyes.gif" /> </p> </body> </html> <?php if($datauri_supported) { function create_data_uri($matches) { $filetype = explode('.', $matches[2]); $filetype = strtolower($filetype[count($filetype) - 1]); if(!preg_match('/^(gif|png|jp[e]?g|bmp)$/i', $filetype)) { return $matches[0]; } if(preg_match('/^//', $matches[2])) { $matches[2] = $_SERVER['DOCUMENT_ROOT'] . $matches[2]; } @$data = base64_encode(file_get_contents($matches[2])); return $matches[1] . "data:image/$filetype;base64,$data" . $matches[3]; } $html = ob_get_contents(); ob_end_clean(); $html = preg_split("/r?n|r/", $html); while(count($html) > 0) { $html[0] = preg_replace_callback("/(src=["'])([^"']+)(["'])/", 'create_data_uri', $html[0]); $html[0] = preg_replace_callback("/(url(['"]?)([^"')]+)(["']?))/", 'create_data_uri', $html[0]); echo $html[0] . "rn"; array_shift($html); } } ?> 

Как все это работает

Сердцем всего этого является возможность создания URI data с использованием данных изображения в кодировке base64.

Но помимо этого, есть пара ключевых трюков, которые нам нужны, чтобы все это работало. Во-первых, используя выходной буфер для предварительной компиляции выходного источника, мы можем снова проанализировать его перед отправкой в ​​браузер.

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

Во-вторых, для реализации фактического разбора я использовал функцию preg_replace_callback , которая идентифицирует пути HTML и CSS с помощью пары регулярных выражений и пропускает их через процесс, слишком сложный для простой замены. (Мы должны искать атрибуты src и свойства url отдельно, потому что синтаксис слишком отличается для одного регулярного выражения, чтобы генерировать идентичные массивы совпадений.)

В функции обратного вызова мы сначала должны определить тип файла, который необходим для выходных данных, а также как условие для разрешенных типов, чтобы мы могли отклонить все, что не является изображением (например, скрипт src ). Массивы $matches match, которые передаются в функцию, всегда содержат полное совпадение подстроки в качестве первого члена (за которым следуют обратные ссылки из [1] ), поэтому, если мы идентифицируем файл, который нам не нужен, мы можем просто вернуть первое совпадение без изменений, и были сделаны.

Единственное, что нужно сделать после этого, это проверить пути к корневому веб-каталогу, для чего потребуется добавить DOCUMENT_ROOT чтобы создать пригодный путь к файлу. Получив все это, мы можем захватить и закодировать изображение (с подавлением ошибок в случае, если исходный путь был поврежден), затем скомпилировать и вернуть URI data . Слишком легко!

Когда оптимизация не является оптимизацией?

Когда стоимость больше, чем экономия! И, конечно, такой процесс не бывает бесплатным — есть несколько потенциальных затрат, которые мы должны рассмотреть.

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

Кроме того, при каждом преобразовании возникают дополнительные затраты на обработку, тем более что размер файла увеличивается. При его загрузке не возникает значительных задержек, если он находится на том же сервере, но кодирование base64 является сравнительно дорогим процессом .

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

Допустим, например, что вы хотели обрабатывать только изображения GIF , это легко сделать, просто изменив регулярные выражения разрешенных типов файлов:

 if(!preg_match('/^(gif)$/i', $filetype)) { return $matches[0]; } 

В качестве альтернативы, вы можете использовать функцию filesize для фильтрации по размеру и переходить к преобразованию только для тех, кто ниже определенного порога:

 if(filesize($matches[2]) > 1024) { return $matches[0]; } 

Что касается больших изображений, я прочитал, что браузеры ограничивают размер URI data ; однако я не наблюдал таких ограничений на практике. Firefox, Opera, Safari и даже IE8 были совершенно счастливы, отображая данные изображений размером более 1 МБ . Наращивая тесты, я столкнулся с ограничением памяти PHP, не получив жалоб от браузеров! Либо я упускаю суть целиком, либо нет ограничений по размеру.

Запад Хо!

Во время экспериментов я тоже попробовал JavaScript и CSS ; однако это не сработало в Internet Explorer, поэтому я больше не стал заниматься этим.

Но по-настоящему интересной разработкой было бы выяснить, возможно ли разработать какой-то алгоритм, который рассчитывает затраты и выгоды от внедрения этой техники в различных ситуациях. Принимая во внимание, возможно, размер и сложность самой страницы, количество и размер каждого изображения, соотношение повторяющихся изображений CSS к изображениям специального контента и время, необходимое для преобразования и кодирования, по сравнению со средним запросом сети , Затем соберите все это вместе, чтобы выяснить, какие изображения выиграют от преобразования, а какие лучше оставить без изменений . Если бы мы могли сделать это последовательным, но автоматическим способом, у нас был бы довольно изящный плагин для WordPress, эй!

Но, честно говоря, я действительно не знаю, где вы начали бы разрабатывать что-то подобное! Есть несколько не поддающихся количественному определению, и много суждений. Хотя, безусловно, есть над чем подумать; и, возможно, вы — мой дорогой читатель — можете предложить свежую информацию?

Миниатюра кредит: Стефан