5 заметок с тегом

mhtml

Кроссбраузерный data URI в CSS-файлах (подробно)

Кстати, всё забываю порекомендовать статью Banderlog’а, который написал её по мотивам моей статьи «Кроссбраузерный data URI в CSS-файлах», прекрасно разложив всё по полочкам и детально проработав тот же подход в форматах PNG и GIF.

Статья на Хабре называется «К вопросу о кроссбраузерных Data URI».

Кроссбраузерный data URI в CSS-файлах (многабукв)

Прелюдия

Некоторое время назад, я нашёл способ, использования data URI в Internet Explorer. Data URI — специальный вид URL, который включает в себя код самого объекта. Например, таким образом можно внедрять изображения прямо в код HTML-страницы.

Выглядит такой HTML вот так:

<img src="data:image/gif;base64,R0lGODdhMAAwAPAAAAAAAP…и так далее" />

Где «data» — это протокол URI, image/gif — тип объекта (картинка GIF), base64 — тип кодирования, после запятой идёт сам код картинки.

Такой вид URI понимают сейчас все современные браузеры, включая Internet Explorer 8 (правда, со многими ограничениями). Плохо то, что Internet Explorer 7 и ниже этого не поддерживает, а доля этих версий настолько велика (около 50% на данный момент по данным LiveInternet), что до недавнего времени data URI использовалась крайне редко.

Я, увлёкшись исследованием малодокументированного протокола mhtml (MIME HTML), поддерживаемого в браузерах Microsoft с пятой версии, в одном из экспериментов разобрался как внедрить объект в тело HTML-документа и, что важнее, придумал как совместить оба способа (data URI и MHTML) в одном документе.

Вообще mhtml был придумал для адресации внутри так называемых «веб-архивах» — формат, очень похожий на формат писем электронной почты и предназначенный для сохранения веб-страниц вместе с картинками, стилями, JavaScript и прочим в одном файле. Сейчас он, так или иначе, поддерживается большинством браузеров.

Типичный mhtml-адрес выглядит следующим образом:

mhtml:http://example.net/index.html!fhhryregdy4gere

Где после «mhtml» идёт обычный веб-адрес, а после восклицательного знака — идентификатор объекта внутри веб-архива. Как я уже сказал, веб-архив очень похож на почтовое сообщение, где один из методов кодирования объектов — base64, тот же самый, который применяется и в dataURI.

В итоге, когда мне пришла эта идея, я совместил оба способа так, чтобы Internet Explorer видел файл как веб-архив с закодированным base64 объектом, а остальные браузеры видели этот закодированный объект как часть dataURI.

Специфика HTML допускает переносы, в том числе и внутри data URI, поэтому мне удалось задуманное относительно быстро. В CSS всё сложнее, а тема использования data URI в CSS, как оказалось, актуальнее, чем для HTML. Например, в книге Николая Мациевского «Разгони свой сайт» сделана попытка расширения моего способа на CSS, к сожалению, менее удачная, чем хотелось бы: требуется включение закодированного объекта два раза.

Фуга

Итак, мне захотелось посмотреть, не получится ли расширить мой способ на CSS, не прибегая к включению закодированного файла два раза. Причём, сфера тут уже, подключаемый к CSS объект это, в 99,99% — картинка.

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

Тем не менее, других способов побороть IE я не видел и потому продолжал эксперименты. Несколько слов о том, что такое base64. Base64, по сути, анахронизм, доставшийся нам от электронной почты на заре её зарождения. Письма тогда ходили исключительно в латинице и все почтовые системы полагали, что никаких других символов в письмах не бывает.

Таким образом, когда встала задача передавать в письмах что-то кроме англоязычных текстов (например, файлы или тексты на других языках), было придумано несколько способов кодирования таких объектов. Один из них — base64 (для математиков — это позиционная система счисления с основанием 64). 64 — это количество знаков, которая использует это кодирование. Это символы A-Z, a-z, 0-9 и плюс со слешем.

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

Итак, строка data URI начинается с чего-то вроде «data:image/gif;base64,» и в CSS не терпит переносов, а MHTML требует, чтобы кодированная строка начиналась с начала строки.

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

Оставим в покое XBM, BMP, ART, WMF/EMF, JPEG200 и прочую экзотику, поддерживаемую браузерами, и остановимся на триплете JPEG, GIF, PNG. Честно сказать с форматом GIF я знаком плохо, а JPEG и PNG исследовал несколько лет назад довольно подробно.

JPEG и PNG состоят из секций. В общих чертах, сначала идёт заголовок файла, потом сами секции, в формате (с некоторыми вариациями) «имя секции», «длина секции», «конец секции». И у JPEG, и у PNG есть секции для произвольного содержимого. У JPEG это COM и APPn, у PNG — iTXt, tEXt и zTXt. Кстати, и у GIF тоже есть похожее место — Application Extension.

Отсюда идея: Internet Explorer, декодировав файл с начала строки, получает заголовок с секцией из содержимого, куда помещается заголовок для остальных браузеров и мусор, оставшийся от декодирования начала data URI, остальные браузеры должны пропустить заголовок для IE и начать декодирование с data URI.

Получаем, на примере JPEG:

FF D8 — заголовок JPEG для IE FF E0 xx xx — секция APP0, куда прячется всё до данных изображения, «;background-color:url(data:image/jpeg;base64,» — это видят остальные браузеры FF D8 — начало JPEG для остальных браузеров «данные изображения» — это место видят уже все браузеры

Все бинарные данные выравниваются и кодируются (строка начала CSS-стиля для остальных браузеров не кодируется и IE декодирует её как мусор внутри APP0).

В моём примере такой CSS выглядит вот так:

/9j/4AAs;background-image:url(data:image/jpeg;base64;BOLKNOTE.RU,/9j/4AAC…

Легко увидеть, как видит этот стиль браузер: «/9j/4AAs» кажется ему ошибочным атрибутом (его, кстати, можно взять в комментарий), а «;» — разделителем атрибутов. И хорошо просматривается («/9j/AA») двойной старт заголовка JPEG-файла. IE начнёт декодирование с первого заголовка, другие браузеры — со второго.

Остальное должно быть понятно из примера («!ie» — CSS hack, делающий стиль видимым только для IE), как это выглядит в браузере, можно увидеть в разделе «Храню» у меня на сайте.

2009   css   datauri   ie   mhtml   программирование

data URL в IE

Пару лет назад я занимался проблемой data URL в Internet Explorer, добился определённых результатов, но то, что получилось, использовать было невозможно. Data URL (иногда его ещё называют «протокол data:») — возможность вставлять ресурсы (графику, CSS, JavaScript и так далее) в HTML код.

Выглядит это примерно так:

<img
src="data:image/gif;base64,R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAw
AAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFz
ByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSp
a/TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJl
ZeGl9i2icVqaNVailT6F5iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uis
F81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PH
hhx4dbgYKAAA7" />

В Data URL указывает тип содержимого и способ его кодирования. Способа кодирования два — base64 (именно он и указан в примере), увеличивающий содержимое на треть и URL encoding (в этом случае способ кодирования не указывается) — привычный многим способ кодирования при помощи %xx, в лучшем случае вообще не увеличивает размер содержимого, в худшем — увеличивает в три раза.

Кстати, в настоящее время IE8b1 поддерживает data URL, но, увы, длиной не более 32Кб.

Теперь амбула.

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

Результат — готовый код на PHP из двух функций. Первую функцию («bolk_data_uri_header») нужно вызвать в самом начале перед выводом любого вашего кода, вторую («bolk_data_uri») собственно для включения картинки в код. Надеюсь на примерах всё понятно:

bolk_data_uri_header();

bolk_data_uri('myjpeg.jpg');
bolk_data_uri('ourpng.png', 'border: 2px dotted red');

Код самой библиотеки:

function bolk_data_uri_header()
{
    echo "<!--\n"
        ."Content-Type: multipart/related; boundary=\"=_NextPart_01C6A9B1.539AB070\"\n\n"
        ."--=_NextPart_01C6A9B1.539AB070\n"
        ."Content-Transfer-Encoding: base64\n"
        ."Content-Type: text/html\n"
        ."-->\n\n";

}

function bolk_data_uri($file, $style = '')
{
    if (!( file_exists($file) && ($data = @getimagesize($file)) )) return false;

    $name = uniqid('', true);

    if ($style <> '') $style = ' style="'.htmlspecialchars($style).'"';

    echo "<!--\n"
        ."--=_NextPart_01C6A9B1.539AB070\n"
        ."Content-Location: {$name}\n"
        ."Content-Transfer-Encoding: base64\n"
        ."Content-Type: {$data['mime']}; -->\n"
        ."<object data='data:{$data['mime']};base64,\n\n";

    echo base64_encode(file_get_contents($file));

    echo "' {$data[3]}{$style} type='\n{$data['mime']}'><img "
        ."src='mhtml:http://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}!{$name}' {$data[3]}{$style} /></object>\n\n"
        ."<!--\n"
        ."--=_NextPart_01C6A9B1.539AB070-->";

    return true;
}

У данного метода, по сравнению с обычными data URL есть масса ограничений: необходим специальный заголовок в начале файла, т. е. этот метод невозможно использовать на чужих сайтах, этим методом нельзя воспользоваться (по крайней мере не в таком виде) для включения ресурсов внутрь CSS или JavaScript. В принципе, тут есть достаточно большое поле для экспериментов, возможно все или некоторые из проблем можно решить.

В разделе «храню» есть пример, который можно потестировать на имеющихся у вас браузерах. Уже протестировано на Opera 9.25 и 9.50b, FF 2.0.0.13 и 4 (Minefield), Safari 3.1 (Safari под iPhone — тоже), Internet Explorer 6.0SP2 и 7.0, Sony PSP browser, Opera mini, Netscape Navigator 9.0.0.5.

MHTML

В предыдущей статье я описывал способ для Internet Explorer, позволяющий внедрить картинки в HTML-код. К сожалению, у него нашёлся недостаток — файл, в котором содержатся ресурсы, должен иметь расширение «mht». Иначе в некоторых версиях Windows браузер ничего не покажет.

Если подумать, это не сильно меняет дело — всего-то придётся выделить «упаковку» картинок в отдельный файл и подключать по мере необходимости. Например, так:

<img src="mhtml:http://sample.com/package1.mht!image.jpg" />
<img src="mhtml:http://sample.com/package1.mht!another_image.gif" />

и так далее. Основное достоинство способа — объединение нескольких картинок в одном соединении и его удаётся сохранить.

Сегодня я расскажу как добиться мультибраузерности при помощи SSI. В других скриптовых языках такой проблемы, понятно, не возникает. Я буду рассматривать SSI веб-сервера Apache, для других серверов, думаю, адаптировать код можно будет без особых проблем. Странно, SSI, этот простой и быстрый язык, который часто отлично подходит для организации нагруженных сайтов, где хотелось бы держать информацию структурированно, почему-то считают недостаточным для программирования. Тем не менее, на нём можно делать достаточно сложные вещи (я как-то писал «песню про пиво» на этом языке, загляните в раздел «99»).

Символом «собачки» (@) я буду выделять места, где перевод строки не нужно ставить — просто мне приходится разбивать длинные места на части. Описание, если не интересно, можно и не читать, просто скопировать всё в нужный файл и положить в каталог /images/.

В настройках Apache или .htaccess должна быть включена обработка SSI в файлах .html и .mht:

AddHandler server-parsed .html .mht

Первый файл (или файлы), который понадобится — это файл самой картинки. Он должен начинаться с полного имени картинки, плюс расширение .html. Например, «1.jpg.html», внутри нужно указать MIME-тип картинки в переменной «TYPE» и после include пометить код картинки в base64:

<!--#set var="NAME" value="$QUERY_STRING" -->@
<!--#set var="TYPE" value="image/jpeg" -->@
<!--#include virtual="pict.html" -->@
4AAQSkZJRgABAgAAZABkAAD...код картинки...mJiBFoxCGkShUxyGCMYMRcSwwRiMdRZ

Следующий файл — img.html. В нём проверяется из какого браузера его вызывают — Internet Explorer или всех остальных (вторым условием убирается Opera, которая иногда притворяется IE) и, в зависимости от этого, выводит, либо ссылку на mht-файл (у меня в коде считается, что все эти файлы лежат в каталоге /images), либо data URL, который генерирует pict.html:

<!--#if expr="$HTTP_USER_AGENT=/MSIE/ && $HTTP_USER_AGENT!=/Opera/" -->@
mhtml:http://<!--#echo var="HTTP_HOST" -->/images/package1.mht!<!--#echo var="QUERY_STRING" -->@
<!--#else -->@
<!--#include virtual="$QUERY_STRING.html?$QUERY_STRING" -->@
<!--#endif -->

Внутри файла package1.mht ссылки на файлы картинок (обратите внимание на параметры вызова в include virtual), в константе BOUND задаётся boundary (разделительная строка), о которой я говорил в прошлый раз. В include надо перечислить (пример показан для двух файлов) все файлы картинок, которые будут входить в этот набор.

<!--#set var="BOUND" value="Next_Section" --><!--
MIME-Version: 1.0
Content-Type: multipart/related; boundary="<!--#echo var="BOUND" -->"

--<!--#echo var="BOUND" -->
Content-Location: index.htm
Content-Transfer-Encoding: base64
Content-Type: text/html

<!--#set var="MHT" value="1" -->
<!--#include virtual="1.jpg.html?1.jpg" -->
<!--#include virtual="2.jpg.html?2.jpg" -->

--<!--#echo var="BOUND" -->-->

Последний файл — pict.html. Вызывается он из файлов картинок. Если он вызывается из mht, то выдаёт кусок mht-кода для данной картинки, иначе — data URI.

<!--#if expr="$MHT" -->--<!--#echo var="BOUND" -->
Content-Location: <!--#echo var="NAME" -->
Content-Transfer-Encoding: base64
Content-Type: <!--#echo var="TYPE" -->

<!--#else -->data://<!--#echo var="TYPE" -->;base64,<!--#endif -->

Теперь, как применять эту груду файлов. Всё очень просто — просто вставляем в нужно место следующий тег:

<img src="<!--#include virtual="/images/img.html?2.jpg" -->" />

И смотрим на результат. В исходном коде в IE мы должны увидеть ссылку на package1.mht, в остальных браузерах — data URI, во всех браузерах — картинку.

2006   ie   mhtml   prog   программирование

Колдуем с MHTML

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

В некоторых версиях Windows XP, 2000 (закономерность не выявлена, но, видимо, дело в наличии каких-то Service Pack) и, кажется, во всех 2003, способ не работает, если расширение файла не mht. Это несколько сужает сферу применения данного способа.

Обход этой проблемы читайте в следующей статье. Далее сама статья:

Сегодня немного поколдуем, ок? Тем, кому не интересна кухня, где готовят внутренности сайтов, могут перейти к просмотру чего-то более интересного, остальные оставайтесь.

Браузеры всё больше превращаются в комбайны, которые поддерживают десятки схем доступа к данным, от всем известных file, ftp, http, https, до полузабытых gopher, finger, news и так далее. Среди этого списка есть интересная схема — data, который поддерживают уже, кажется, все браузеры, кроме Internet Explorer (включая беты седьмых версий). Для тех, кто незнаком с ним, общий вид строки запроса в этой схеме выглядит так:

data://[mime-тип;][тип кодирования],данные...

смысл в том, что данные (картинки, javascript, другие ресурсы) можно помещать внутрь HTML. Эта схема является частью стандарта HTML 4.0, если я не ошибаюсь, но, в силу того, что она не поддерживается основным игроком на рынке браузеров, используется лишь эпизодически — чаще всего в интранет-приложениях и расширениях к Opera и Mozilla FireFox. Выглядит это, примерно, так:

<img src="data:image/png;base64,iVBORw0KGgoAAAAN...и так далее">

Эта схема, кроме возможности помещать различные данные в HTML, имеет и более очевидные преимущества. Обычно, браузер открывает не более 2-4 соединений с сервером, через которые и получает данные. Если на странице очень много мелких картинок, браузер не имеет возможности загрузить и показать их одновременно — приходится ограничиваться означенным количеством соединений.

Мы, в нашей студии, обычно стараемся использовать карту ссылок, вместо нескольких мелких кнопок, например, для ускорения загрузки. Я очень много разговаривал с поисковиками Google и Yandex, но они уверены, что в IE внедрение ресурсов в HTML невозможно. Так ли это?

Нет. По крайней мере мне удалось найти минимум один способ сделать это. В интернете я ничего подобного не встретил, так что будет считать, что его изобрёл я. Натолкнула меня на эту мысль одна из кошмарных домашних страниц, которую сверстали в Microsoft Word и сохранили... в формате MHTML.

Этот формат первым начали использовать в браузере от Microsoft (поэтому его иногда расшифровывают как «Microsoft HTML») для того, чтобы можно было сохранять текст со всем содержимым — стилями, изображениями в одном файле, поэтому этот формат ещё называют веб-архивом (Web Archive). Чаще же всего его название расшифровывают как MIME HTML и в этом заключается главная проблема.

MIME HTML сильно отличается от обычного HTML форматом — внутри заголовок, информация об именах и форматах файлов, кодированные файлы изображений и так далее — более всего это похоже на файлы писем с вложениями (посмотрите исходный текст письма в своём почтовом клиенте). Нам было бы интересней, если бы в остальных браузерах показывалось бы что-то более похожее на HTML.

Просматривая веб-архив в браузере, я обнаружил, что IE отображает файлы архива через специальную схему — mhtml. У него есть интересная особенность — он позволяет адресовать файлы внутри веб-архива извне, вот так:

mhtml:http://example.com/file.mht!image.jpg

Причём, оказывается, браузер довольно вольно подходит к формату файла, лишь бы внутри были необходимые секции, что позволяет легко заключить архив внутрь HTML-комментария:

<!--
Content-Type: multipart/related; boundary="=_NextPart_01C6A9B1.539AB070"

--=_NextPart_01C6A9B1.539AB070
Content-Location: index.htm
Content-Transfer-Encoding: base64
Content-Type: text/html

--=_NextPart_01C6A9B1.539AB070
Content-Location: img.jpg
Content-Transfer-Encoding: base64
Content-Type: image/jpeg

4AAQSkZJRgA....закодированная...base64...картинка...BAgAAZABkAADMdRZ

--=_NextPart_01C6A9B1.539AB070-->

<body>
<img src="mhtml:http://www.test/index.html!img.jpg">
</body>

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

Content-Location — имя файла внутри архива;
Content-Tranfer-Encoding — формат кодирования файла, экономичнее всего использовать base64;
Content-Type — MIME-тип файла;

если вы не знаете, что такое base64 и MIME-тип, то лучше обратиться к поисковику — такие вещи лучше знать. Первая секция с типом «text/html» — обязательная, без неё Internet Explorer не отобразит ваши файлы, остальные могут содержать данные любых типов. Ссылка может указывать на тот же самый или любой другой файл, который содержит веб-архив.

Я не уверен насчёт четвёртой версии браузера, но 5.0 и выше должны отобразить такой HTML нормально. У этого способа есть преимущество перед схемой data — на файл можно ссылаться несколько раз. Организация вывода разного контента для двух групп браузеров — сейчас довольно простая задача. Лучше всего, конечно, использовать для этого язык на стороне сервера (для JavaScript объёмы данных слишком значительные) — например, SSI или PHP.

Кстати, Opera 9 так же поддерживает веб-архивы, но использует для этой цели другую схему — «attachment». Но Opera 9 слишком мало распространена и, кроме того, этот браузер давно поддерживает схему «data».

Ещё хотелось бы упомянуть, что Internet Explorer поддерживает ещё одну схему, через которую, вероятно, можно получить доступ к ресурсам веб-архива — «mid», но я в этом не уверен и никаких экспериментов по этому поводу не проводил.

2006   ie   mhtml   prog   программирование