Умудренный опытом читатель, прочитав заголовок статьи, сразу, конечно, поймет, что это - не больше, чем шутка и речь дальше пойдет о какой-нибудь программке на JavaScript, которая поможет вам получить еще одного посетителя на сайт, или создаст интерфейс "с человеческим лицом", или...
Что я могу ответить такому проницательному читателю? Только одно: вы ошиблись! Именно созданием человека мы сегодня займемся - в самом что ни на есть прямом и нешутейном смысле этого выражения. Не больше, но и не меньше. Конечно, реализация мечты средневековых алхимиков о гомункулюсе - не самая простая программистская задача. Но, согласитесь, скучно заниматься мелочами...
Кому и верить в чудо, как не программисту? Каждый раз, когда мы пишем новую программу - мы создаем новый мир. А сегодня я обещаю научить вас настоящему волшебству: из обрывков программного кода и пригоршни медной проволоки мы сотворим реального человека. Настоящего, живого, дышащего и мыслящего человека, который до этого никогда не жил на Земле и который появится из ниоткуда по нашей воле. Хотите? Нет ничего проще! Все, что от нас потребуется - вера в свои силы, пару строк заклинаний на JavaScript и полчаса работы. Итак, приступим…
Позвольте мне начать немного издалека. Сначала сказанное ниже вам может показаться лирическим отступлением, но потом вы увидите, что на самом деле это - постановка задачи.
Итак, скажите, пожалуйста, какие ассоциации возникают у вас в связи со словом "платить"? Рубли, доллары, фунты? Тогда вы, к сожалению, ошиблись еще раз. В действительности, получая что-либо, мы всегда рассчитываемся за приобретение своей жизнью. Если мы неделю (месяц, год) зарабатывали на цифровую фотокамеру, потом пошли в магазин и вышли оттуда с покупкой в дрожащих от счастья руках - это значит, что неделю (месяц, год) своей жизни мы обменяли на возможность орать: "сейчас вылетит птичка!". Покупая, беря в кредит, обменивая любой товар, мы платим за него определенным количеством часов своей жизни. То же самое можно сказать по-другому: мы уменьшаем продолжительность своей жизни в обмен на приобретенные вещи. Вместо того, чтобы пойти в лес, на пляж, почитать хорошую книгу, мы тратим время на зарабатывание денег, то есть, попросту - добровольно вычеркиваем из жизни оговоренное количество дней, за что нам потом дадут новый компьютер, телевизор или квартиру...
Согласны, что ситуация катастрофична? Тогда давайте искать из нее выход.
Для этого разрешите мне нарисовать еще одну картинку (уже не столь апокалипсическую). Что происходит, когда мы открываем страницу какого-нибудь сайта? Если вы на минуту задумаетесь, то поймете, что при этом случаются вещи удивительные. Давайте представим: вы сидите за компьютером с быстродействием в сотни миллионов операций в секунду, который через модем прикручен к Интернету. Вы набрали в браузере нужный адрес и стартовали загрузку страницы. "Пожар!" - кричит ваш компьютер на всю сеть. - "На помощь!".
Призыв не остается без ответа. Мордатые двухметровые плечистые пожарные - вэб-серверы, маршрутизаторы, роутеры и прокси - сбегаются на зов, выстраиваются друг за другом, и... начинают передавать по цепочке наперстки воды. Да если бы наперстки!.. Если сравнить сотни миллионов операций в секунду с обычной скоростью модема (4-12 КБайт/с), то можно сказать, что между получением соседних байтов для компьютера проходят целые геологические эпохи. А пользователь, сидящий у монитора, отдает каждый раз тридцать секунд, а то и пять минут своей жизни в ожидании загрузки.
Конечно, вы уже поняли: в наших силах продлить жизнь всех пользователей вашего сайта, ускорив загрузку страниц. Представляете, что будет, если нам удастся сэкономить в среднем по пять секунд на клик на сайте со средним числом хитов - скажем, 15000 в сутки? Тем самым мы с вами вернем человечеству больше, чем двадцать четыре часа времени осмысленной жизни в течение суток. Понимаете, что это значит? Мы вызовем к жизни еще одного жителя Земли! Будем верить, что новый человек, которого мы с вами сотворим таким образом, не окажется крэкером ;) ...
И теперь, когда вы осознали все величие стоящей перед нами цели, давайте, наконец, закончим вступление и приступим к решению этой благородной задачи.
Раз скорость канала от нас не зависит, остается единственный и очевидный путь: уменьшать вес страницы. Тем самым мы, кстати, вдвойне поможем обладателям платного трафика: они будут не только меньше ждать у монитора, но и быстрее зарабатывать на просмотр того же количества страниц сайта. Прямая выгода от этого и нам: при размещении сайта на виртуальном хостинге мы всегда сталкиваемся с такой неприятной штукой, как ограничение на количество одновременно выполняемых процессов. Это значит, что, пока, например, пять юзеров радостно утаскивают ваши страницы к себе домой, остальные в лучшем случае "подвисают" в очереди, а в худшем - получают сообщение об ошибке. И уменьшая вес страницы, мы тем самым уменьшаем вероятность подобной ситуации.
Итак, что мы можем? Первый и очевидный шаг: ругаемся с дизайнером и выбрасываем всю его графику неземной красоты и непомерного веса. Оставляем простой дизайн, легкие кнопочки и маленькие иконки.
Но вот все это сделано. Остается почти голый HTML. На рис.1 и 2 показана страница с «минималистским» дизайном - вид снаружи и изнутри. Как видно, если ваш сайт - это не простейшая галерея, а, скажем, форум, архив статей или любой другой вариант, где преобладает текстовый контент, и/или вы используете "навороченный" движок, то сам HTML без графики тянет тоже немало - до 500Кб.
Рис. 1. Легкий дизайн, всего 2,8 кБ текста на странице...
Рис. 2. ...и 95 кБ в результате! Как видим, «КПД» такой страницы - около 3%.
Особенно плохо обстоят дела на новостных порталах. Сопоставьте, к примеру, объем отображаемого текста - меньше 2кБ (рис.3) с весом загружаемой страницы - 165 кБ только html-кода (рис.4) для далеко не самого перегруженного портала http://gazeta.ru.
Что можно предпринять? Конечно, обязательно паковать, по каналу гнать сжатый HTML, и распаковывать его на конечном компьютере! Вопрос только, как это делать и чем.
Рис. 3. Текстовый контент этой страницы - меньше 2 кБ.
Рис. 4. Пока мы догрузим эти новости - они будут позавчерашними.
Если ваш стаж в сайтостроении больше пяти лет, то вы не можете не помнить энтузиазма, с которым было встречено появление XML. Основное преимущество этого языка всеми мыслилось в том, что он способен радикально уменьшить трафик, переложив основную часть обработки и форматирования информации с сервера на компьютер клиента.
И вот XML уже применяется на полную катушку. Да только одна незадача: если почитать последние статьи по этому языку, в каждой из них мы наткнемся на довольно неожиданный рефрен: "XML - это великий и могучий язык обработки и форматирования данных, это - один свет в окошке, рай земной и коммунизм в одном флаконе; но, естественно(?!), платой за его неизмеримые достоинства является здорово увеличенный(!) по сравнению с HTML размер результирующего файла."
Грустно... Что же, не будем опускать руки. Есть ведь и другой способ: многие современные браузеры умеют работать с упакованными файлами. В числе других заголовков http-запроса браузер посылает серверу и список форматов компрессоров, которые он понимает.
Но тут опять не все так просто. Во-первых, разные браузеры поддерживают разные форматы (а то и вообще никаких). А во-вторых, если вы достаточно легко можете уговорить свой Apache эти файлы не перекодировать, то, пройдя через прокси, они, как правило, все равно превращаются во что-то, мягко говоря, своеобычное. Стало быть, этого делать нельзя: своими руками отсекать 10-15% потенциальных посетителей сайта - так же непрофессионально и по-детски, как писать сайты, корректно работающие только под Internet Explorer-ом.
Выходит, и тут мимо. Что же, не будем ждать милости от софтверных монстров и посмотрим, что можем сделать мы сами.
Есть среди используемых в сайтостроении языков один невзрачный и непритязательный. Да еще и в почтенном возрасте. Но именно к нему мы обратимся за помощью. В отличие от XML, язык JavaScript, предназначенный для обработки информации на стороне клиента, не обещает приносить вам тапочки и кофе в постель и писать за вас сайты в полпинка, но зато и в самом деле делает то, для чего был придуман. А значит, нужен нам именно он.
Допустимо ли использовать JavaScript в принципе? Я провел небольшое исследование на своем сайте на этот счет. Оказалось, что из более чем восьми тысяч отслеженных хостов JavaScript был отключен только у трех человек! Так что, наверно, вполне достаточно в тэгах <noscript> попросить этих троих (которые составили 0,0036% всех визитеров!) его включить. Но, конечно, если для вас это принципиально - кто мешает сделать проверку поддержки и оставить параллельно новому и существующий вариант сайта - эксклюзивно для лиц, повредившихся на почве сетевой безопасности? Тем более, что понадобится нам не пятая, - с поддержкой DOM, - а самая первая версия этого языка, так что нашим сайтом смогут насладиться даже самые изысканные эстеты - утонченные ценители антикварных браузеров.
Давайте посмотрим на любую страницу профессионально сделанного сайта. Практически наверняка мы увидим множество повторяющихся на ней элементов. Как правило, это вложенные таблицы, в строках которой выводится текст или картинки. Вот этими повторяющимися тэгами мы и займемся. Текст, за редкими исключениями, трогать нельзя - хотя бы потому, что мы хотим, чтобы наша страница нормально индексировалась поисковыми ботами, половина из которых в JavaScript - ни в зуб копытом. Но не переживайте: могу сказать, что, убрав только дублирующиеся тэги, размер HTML-кода реальной страницы можно сходу уменьшить от двух до десяти раз!
Начать нам будет проще всего с конца - с готовой HTML страницы. Будем пока считать, что ваш сайт состоит из набора статических страниц.
Итак, вы открываете страницу и видите там примерно такое безобразие, как показано на первой врезке, за которым идет еще сотня строк (блоков "<tr>...</tr>") этой таблицы, отличающихся друг от друга только теми элементами, которые мы на врезке подчеркнули.
Напрашивается очевидный выход из положения: эту таблицу нужно формировать уже на стороне получателя, а в коде страницы вместо нее гнать только выполняющую эту нехитрую операцию программку на JavaScript, передавая ей данные, которые нужно подставить в ячейки таблицы.
Собственно, на этом месте можно было бы и закончить статью, так как реализация такого алгоритма не составляет никакого труда. Поэтому дальше я только для начинающих покажу, как примерно должна выглядеть соответствующая JavaScript-программа.
Первым делом, выберем разделитель между соседними строками. Им может служить любой символ, который вы заведомо не используете при выводе внутри нужной нам таблицы. Пусть это будет, скажем, "|".
Из html-файла выбрасываем все, что находится между тэгами <table> и </table> таблицы, которую мы обрабатываем. Вместо этого пишем там же <script>...</script> - контейнер, внутри которого будет находиться наша JavaScript-программа.
Все, что на врезке подчеркнуто (то есть, все меняющиеся данные), мы гуртом загоняем в переменную, разделяя выбранным символом "|" соседние значения и заканчивая строки html-таблицы парой "||". И такой же разделитель поставим в самом начале текстовой строки:
x="|4|4|4|live|Иван Васильевич Грозный|23.02.1635||35|35|35|Билл ... ||"
Все, что нам осталось - написать простой код, восстанавливающий таблицу. Например, такой, как показано на второй врезке.
Давайте разберемся, как это работает. Если посмотреть на исходный html-код строки таблицы, который нам нужно вывести, видно, что он состоит из 7 кусков неизменных данных, перемежающихся шестью переменными данными (последние мы выделили красным).
Наша программа в цикле for читает по одному символу переменной x. Если считан обычный символ - присоединяет его к выходной строке o. А если считан разделитель - выводит в выходную строку очередной неизменный кусок.
Но как определить, какой кусок "очередной"? Для этого мы ввели переменную n и организовали несколько необычный неявный цикл на операторе switch. В заголовке этого оператора мы поместили выражение n++%7, состоящее из двух операций. Операция % - это деление по модулю, то есть, целочисленный остаток от деления n на 7. Понятно, что если последовательно увеличивать на единицу значение n, начиная от нуля, остаток от его деления на 7 будет колебаться в диапазоне от 0 до 6, принимая последовательно значения 0,1,2,3,4,5,6,0,1,2...
Вот операция ++ и есть постфиксный инкремент. Если перевести эту тарабарщину на русский язык, она значит, что каждый раз значение n увеличивается (или инкрементируется) на единицу, причем постфиксно, то есть "задним числом" - сначала используется "старое" значение переменной, а уж потом она увеличивается. То есть, раз мы присвоили переменной n начальное значение 0, то, считав символ разделителя, с которого начинается строковая переменная x, программа выполнит оператор выбора switch при значении n=0. Результатом обработки выражения n++%7 будет 0 (так как 0/7= 0 и 0 в остатке), и сработает условие case 0, включив в выходную строку o тэги начала строки таблицы вплоть до меняющейся части. Но (обратите на это внимание!) значение n после выполнения n++%7 станет уже равным 1. Значит, встретив разделитель в следующий раз, программа выполнит уже условие case 1 (ведь 1/7 = 0 и 1 в остатке), а еще один разделитель вызовет сработку case 2 и т.д. Вы конечно, уже поняли, откуда в операторе switch(n++%7) взялась именно семерка. Конечно, это число "константных" кусков html-кода, которые нам нужно поочередно выводить.
Последний оператор нашей программы - это старый добрый метод document.write(), выводящий свой аргумент в то место html-документа, где он написан.
Как видим, получилась универсальная программа, которую можно применять для упаковки любых регулярных частей страницы, меняя только значение переменной x, а также количество и значения выводимых постоянных блоков.
Как быть, если страницы у вас динамические? Точно так же! Эту программу мы можем так же легко заставить формировать наш движок на php или любом другом серверном языке, вместо того, чтобы забивать ее вручную.
И напоследок: конечно, то, что мы с вами рассмотрели, еще нельзя назвать упаковщиком. Это только демонстрация того, как можно при минимальных усилиях сократить трафик. Но возможен и настоящий упаковщик. Основная идея его написания, скорее всего, должна быть такой: скрипт (например, на php) просматривает страницу на предмет повторов и других легко пакуемых мест. А на стороне клиента распаковкой занимается универсальная программка на JavaScript. Такой распаковщик сам по себе может весить пару килобайт, но он может быть размещен в отдельном файле. Тогда он будет читаться один раз и оставаться жить в кэше браузера, обеспечивая удобства именно тем вашим клиентам, которые время от времени заходят на сайт, а не попали случайно из поисковика на главную страницу и тут же ушли навсегда.
Да, такой упаковщик вполне возможен. И я сейчас заканчиваю бета-версию подобной программы. Но в процессе разработки всплыло столько неочевидных тонкостей, что их рассмотрение вышло бы далеко за пределы этой статьи.
Поэтому здесь я должен поставить точку и поблагодарить всех, у кого хватило духа добраться до конца. Всего доброго и удачного кодинга вам!
Врезка 1. Типичный HTML-код:
<table>
<tr>
<td title='редактировать профиль'>
<a href='admin/edit.php?us=4'>
<img border='0' src='http://my.com/design/edit.png' alt='изменить'></a></td>
<td title='удалить из базы'>
<a href='admin/delete.php?us=4'>
<img border='0' src='http://my.com/design/delete.png' alt='стереть'></a></td>
<td title='забанить/разбанить юзера'>
<a href='admin/bun.php?us=4'>
<img border='0' src='http://my.com/design/user_live.png' alt='бан'></a></td>
<td title='Ф.И.О нашего юзера'>Иван Васильевич Грозный</td>
<td title='дата регистрации'>23.02.1635</
</tr>
<tr>
<td title='редактировать профиль'>
<a href='admin/edit.php?us=35'>
<img border='0' src='http://my.com/design/edit.png' alt='изменить'></a></td>
<td title='удалить из базы'>
<a href='admin/delete.php?us=35'>
<img border='0' src='http://my.com/design/delete.png' alt='стереть'></a></td>
<td title='забанить/разбанить юзера'>
<a href='admin/bun.php?us=35'>
<img border='0' src='http://my.com/design/user_bun.png' alt='бан'></a></td>
<td title='Ф.И.О нашего юзера'>Билл Цензоред Гейтс</td>
<td title='дата регистрации'>08.03.1901</
</tr>
Врезка 2. Пример JavaScript-распаковщика
o="";n=0
for (i=0;i<x.length;i++)
{ c=x.atChar(i)
if (c=="|") switch (n++%7)
{case 0: o+="<tr><td title='редактировать профиль'><a href='admin/edit.php?us='";break
case 1:o+="'><img border='0' src='http://my.com/design/edit.png' alt='изменить'></a></td> <td title='удалить из базы'><a href='admin/delete.php?us=";break
case 2:o+="'><img border='0' src='http://my.com/design/delete.png alt='стереть из базы></a></td><td title='забанить/разбанить юзера><a href='admin/bun.php?us="; break
case 3: o+="'><img border='0' src='http://my.com/design/user_"; break
case 4: o+=".png' alt='бан'></a></td><td title='Ф.И.О нашего юзера'"; break
case 5: o+="</td><td title='дата регистрации'>"; break
case 6: o+="</td></tr>"; break;}
else o+=c;}
document.write(o)
Рис. 5. Та же страница, что на рис.4, после упаковки.