Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу).
Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу). Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу). Homepage Карта сайта Версия для печати

Джентльменский набор Web-разработчика   Ларри Уолл о Perl6   Наблы Система Orphus
 

17. Использование перенаправлений,  или внутренний и внешний редиректы, а также самопереадресация

[28 февраля 2002 г.]

Сегодня мы поговорим о такой важной вещи, как переадресация. Это слово, как и множество других компьютерных терминов, необходимо понимать «объемно», а не «поверхностно». То есть, когда речь заходит о переадресации, не нужно лихорадочно представлять себе почтальона с ноутбуком на багажнике велосипеда (не пробуйте — растрясете) — лучше вспомнить об английском слове redirect, что на русский переводится как «редирект», синоним для «переадресация». На мой взгляд, слово «редирект» все-таки удобнее, потому что не навевает побочных ассоциаций, так что в дальнейшем я буду использовать именно его.

В чем же смысл редиректа?.. Он прост. Редирект — это когда запускается какой-то скрипт, и страница, которую увидит в итоге пользователь, окажется сгенерирована не этим, а другим скриптом.

Чайник 

Я очень старался подобрать точное определение, но в конце все-таки решил остановиться на более коротком и сделать пару оговорок. Во-первых, под «скриптом» здесь понимается все, что угодно, а не обязательно программный код: например, переадресация вполне может происходить с одной «статической» страницы на другую. Во-вторых, под «другим скриптом» в действительности надо понимать «другой процесс», потому что скрипт может быть и тем же самым (как при самопереадресации).

Переадресация — это в некотором роде передача полномочий, когда текущая программа на смертном одре объявляет: «Все, не могу больше, уберите это от меня! Пусть страницей займется кто-нибудь еще». Затем она передает управление другому скрипту, а сама тихо и незаметно уходит на покой.

Казалось бы, все просто. Оказывается, не совсем. Но, прежде чем это обсуждать, давайте рассмотрим главный способ переадресации — так называемый «внешний редирект».

Внешний редирект

Это — переадресация, инициируемая браузером по просьбе скрипта. Она выполняется так: браузеру вместо страницы (или в дополнение к ней) передается специальная команда, заставляющая его перейти по указанному URL.

Самый простой и универсальный способ выполнить внешний редирект — послать браузеру тэг <meta>, а затем немедленно завершить работу. Вот как это делается на PHP:

<?
# 0 означает, что переадресация произойдет
# через 0 секунд, то есть, немедленно.
print '
    <meta http-equiv=Refresh 
    content="0; URL=/some/other/script.html">
';
exit();
?>

Это еще иногда называется «перещелкиванием» из-за характерного звука, издаваемого браузером при приеме такого тэга. Второй способ редиректа — послать специальный заголовок Location, но уже не через тэг <meta>:

<?
Header("Location: http://$SERVER_NAME/some/other/script.html");
exit();
?>

Обратите внимание на то, что во втором случае я использовал полный URL, а не один лишь URI, как в предыдущем примере. Сейчас будет ясно, зачем так сделано.

Лирическое отступление 
Я только что проверил, и, похоже, при использовании заголовка Location браузер не издает щелчка. Так что второй способ может быть предпочтительным для людей с обостренным слухом. Учитывайте только, что отправлять заголовки нужно обязательно до посылки текста документа браузеру.

Внутренний редирект

Кроме мерзкого щелчка, внешний редирект имеет еще один недостаток: задержку из-за необходимости по модему передавать туда-сюда данные запроса-ответа. Иными словами, он не проходит незамеченным для браузера. Это означает, что, получив команду на внешний редирект, браузер полностью забывает о предыдущем скрипте и всецело отдает себя служению порожденному запросу. Новый адрес даже появляется в адресной строке окна. Хорошо это или плохо, мы посмотрим чуть позже, а пока посмотрим, как внутренний редирект решает вопрос (вернее, пытается решить).

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

При работе с Apache, насколько я знаю, существует лишь один способ выполнить внутренний редирект: указать в заголовке Location не абсолютный URL, а URI (то есть, URL без имени хоста и порта). Отсюда автоматически следует, что внутренний редирект, в отличие от внешнего, может происходить только в пределах одного сайта:

<?
Header("Location: /some/other/script.html");
exit();
?>

Когда сервер получает от скрипта страницу и собирается отправить ее браузеру, он прежде всего проверяет: нет ли в ней заголовка Location с указанием URI документа. Если есть, то сервер порождает новый процесс — копию самого себя — и велит ей выполнить новый запрос, а о старом «забывает». Повторюсь: все это происходит без участия браузера, который видит лишь конечную страницу с тем же самым URL, который был у самого первого скрипта.

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

Итак, вы вернулись. Хороший знак. Продолжим. Если вы используете «способ третий», описанный в шестнадцатой набле (например, боготворите компанию Sun и пишете сервлеты и JSP-страницы), внутренний редирект будет для вас частым гостем. Честно говоря, в спецификации JSP вообще заявляют, что это единственно удобный способ для больших Web-приложений. Там предлагают примерно такой способ:

  1. При запросе запускается сервлет (читайте — CGI-скрипт, простому смертному отличия несущественны), обрабатывает данные и формирует результат, который необходимо отобразить.
  2. Сервлет помещает эти данные в сессию страницы.

    Лирическое отступление 
    Вообще-то, в книгах принято говорить не «сессия», а «сеанс», но мне последний термин не нравится. Во-первых, он очень напоминает фразу «сеанс у психиатра» или «сеанс одновременной игры». А во-вторых, на английский сеанс переводится как «session», и транслитерация тут вполне уместна. Грубо говоря, сессия — это некоторое хранилище, в которое можно помещать данные, чтобы потом забрать их при следующем запросе (в следующем скрипте).

    Вообще говоря, вместо сессии здесь может использоваться и другой тип хранилища, но, пожалуй, именно сессия будет наиболее простым решением.
  3. Затем сервлет выбирает, какой шаблон нужно использовать для отображения этих данных. (В качестве шаблонов Sun спит и видит страницы JSP.)
  4. Наконец, на выбранный шаблон осуществляется внутренняя переадресация. Шаблон первым делом забирает данные из сессии, а затем работает, как ему нужно.

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

Давайте теперь рассмотрим недостаток внутреннего редиректа. Он всего один: неведение браузера относительно той катавасии, которая в действительности происходит на сервере. Пусть, к примеру, скрипт, выполняющий переадресацию, располагается по адресу /forum/doit.php. Предположим, при его запуске было обнаружено, что пользователь еще не зарегистрировался, а значит, его нужно перенаправить на страницу регистрации /register/new.html. На этой странице присутствуют ссылки на изображения: <img src=login.gif>, причем считается, что картинка расположена в той же директории, что и сама страница (указан относительный путь).

Что же получается?.. Если неавторизованный пользователь запустит скрипт, произойдет внутренняя переадресация на страницу регистрации, однако URL в адресной строке останется прежним — /forum/doit.php. При этом в окне браузера будет отображаться страница регистрации, но браузер-то об этом не знает! А значит, он будет считать, что текущая директория на сайте — /forum/, а не /register/, и, конечно же, не сможет правильно отобразить картинку.

Лирическое отступление 
Похожий пример уже рассматривался в шестнадцатой набле, но он не идентичен примеру выше. Если пользователь зайдет на страницу регистрации «вручную» (набрав ее адрес в браузере), то картинка окажется на месте — ведь он ввел адрес /register/new.html в адресной строке и тем самым установил текущую директорию в /register/. Мы получаем, что одна и та же страница может либо «работать, как ей и положено», либо «не работать» — ужасный симптом при отладке! В этом и заключается отличие: пример из шестнадцатой наблы не позволяет просто так переходить в браузере на файлы шаблонов (в нашем случае — на страницу регистрации), а значит, «не работает» никогда.

Все это особенно задевает за живое, когда используется «дедовское CGI-программирование» с использованием директории /cgi-bin/ для хранения скриптов. В этом случае текущей директорией всегда будет /cgi-bin/, но ведь из нее запрещено читать картинки и все остальное — можно только запускать скрипты!

Решение проблемы (вернее, затычка для дыры) — всегда использовать для изображений абсолютный путь (например, /register/login.gif). Так часто и делается, и иногда это и правда бывает оправдано, но чаще всего — нет. Почему?.. А вы только представьте, как будет кто-то мучиться, если решит переименовать директорию register во что-то еще: нужно будет пройтись по всем файлам и везде поменять абсолютный путь...

Лирическое отступление 
Рекомендую избегать абсолютных путей, но в то же время не использовать и пути вида ../../../somewhere. И то, и другое приводит к лишним зависимостям, которые весьма утомительно исправлять. Как показывает практика, использование .. даже хуже, чем абсолютные пути. Зато вполне допустима конструкция img/login.gif — относительный путь в поддиректорию.

Наверное, вы поняли, что я хочу сказать всеми этими примерами. Внутренний редирект имеет принципиальные уязвимости, даже при использовании «способа третьего» из шестнадцатой наблы. Что уж говорить о «четвертом способе» и «шаблонизаторе» — там внутренняя переадресация вообще практически всегда неприменима. Хорошей альтернативой тут будет лишь внешняя переадресация, хотя она и связана с задержками и (иногда) с «перещелкиванием».

Лирическое отступление 
Вообще, считается, что пользователь любит, когда к нему относятся по-человечески. При этом он быстро привыкает к хорошему. Вы можете в этом убедиться, написав программу, которая на время своей работы превращает курсор мыши в песочные часы. Запустите ее и понаблюдайте за собой: вы заметите странную вещь. Вы начнете испытывать какое-то внутреннее напряжение, увидя «фальшивые» песочные часы. Ха-ха, даже если вы сами писали эту программу, вы все равно будете чего-то ждать! Но вы ведь прекрасно понимаете, что система вовсе не занята обработкой данных, а просто издевается над вами. Вот что значит — сила привычки. То же происходит и с «перещелкиванием». Услышав этот звук, пользователь понимает: ему поддали пинка, в результате чего он перелетел на другую страницу (это при попытке неавторизованного входа). В зависимости от контекста, этот звук будет не обязательно восприниматься негативно, скорее наоборот: человек почувствует, что ему дают знать о происходящем. Вообразите теперь глаза пользователя, который ввел в браузере один адрес, а попал на совершенно другой — из-за внутренней переадресации.

Самопереадресация

Про внутренний и внешний редиректы сказано достаточно, перейдем теперь к так называемому self-редиректу (самопереадресации), когда страница заставляет браузер перейти... на саму себя. Звучит довольно загадочно: зачем это вообще может понадобиться?.. Отвечаю: из-за недосмотра разработчиков протокола HTTP (см. об этом в конце наблы).

Проще всего опять рассмотреть пример. Пусть у нас есть небольшая гостевая книга bad.php, работающая следующим образом: при вызове скрипта без параметров (наборе его адреса в браузере) отображается форма с предложением ввести новое сообщение. Затем идут уже существующие комментарии книги. При нажатии на кнопку текст передается тому же самому скрипту методом POST, при этом комментарий добавляется в книгу и тут же отображается вместе с остальными записями:

<?
$FNAME = "book.txt";
if(@$doAdd) {
    $f=fopen($FNAME,"a");
    if(@$text) fputs($f,$text."\n");
    fclose($f);
}
$GB=@file($FNAME); 
if(!$GB) $GB=array();
?>
<form action=<?=$SCRIPT_NAME?> method=POST>
Текст:<br>
<textarea name=text></textarea><br>
<input type=submit name=doAdd value="Добавить">
</form>

<?foreach($GB as $text) {?>
    <?=$text?><br><hr>
<?}?>

Чайник 

Такой метод передачи данных «самому себе» прекрасно себя зарекомендовал в Web-программировании и полностью соответствует идеологии «способа четвертого» из шестнадцатой наблы. Он имеет множество достоинств, например, отсутствие «некорректных» ссылок и ясность для пользователя. Корпорация Sun, как обычно, выступает против, но это уже совсем другая история. Для сокращения письма я не использую в приведенном выше примере отдельный шаблон, а просто «прилепляю» шаблон в конец скрипта.

Пусть теперь пользователь ввел в браузере адрес bad.php, напечатал текст и нажал на кнопку добавления. Щелкните по ссылке и проделайте это, а затем, когда сообщение будет добавлено, нажмите кнопку Обновить в браузере. Перед вами предстанет диалог с вопросом: точно ли вы уверены, что хотите отправить форму снова?.. Ну правильно — ведь страница с результатом была сгенерирована POST-запросом! Вы попадете в безвыходную ситуацию: если нажмете Да, то ваше сообщение окажется добавленным в книгу дважды, если же ответите Нет, получите от браузера совершенно бессмысленный текст «страница устарела, тра-ля-ля». Но ведь ясно, что вы хотели сделать: вы нажали Обновить, чтобы проверить, не написал ли уже кто-нибудь ответ на ваш комментарий!

Решение — лишь одно: после добавления сообщения в книгу выслать браузеру запрос на внешний редирект — конечно, к тому же самому скрипту. Перенаправление всегда осуществляется методом GET, а значит, кнопка Обновить будет работать, как ей и положено. Все это и есть самопереадресация.

Чайник 

Обратите внимание, что self-редирект не бывает внутренним — он может быть только внешним, иначе этот термин вообще теряет смысл.

Вот как будет выглядеть корректно работающий скрипт:

<?
$FNAME = "book.txt";
if(@$doAdd) {
    $f=fopen($FNAME,"a");
    if(@$text) fputs($f,$text."\n");
    fclose($f);
    $rnd=time(); # ВНИМАНИЕ!
    Header("Location: http://$SERVER_NAME$SCRIPT_NAME?$rnd");
    exit;
}
$GB=@file($FNAME); 
if(!$GB) $GB=array();
?>
<form action=<?=$SCRIPT_NAME?> method=POST>
Текст:<br>
<textarea name=text></textarea><br>
<input type=submit name=doAdd value="Добавить">
</form>

<?foreach($GB as $text) {?>
    <?=$text?><br><hr>
<?}?>

Лирическое отступление 
Настоятельно рекомендую вам обратить внимание на строчку, помеченную комментарием «ВНИМАНИЕ!». Вы можете видеть, что я «прицепляю» в хвост URL текущее время в секундах, выступающее здесь в качестве уникального идентификатора. Такой маневр сильно уродует URL в адресной строке, однако он действительно необходим из-за ошибки в одной из версий браузера Netscape. А именно, при самопереадресации (и только при этом виде редиректа, когда URL остается неизменным) Netscape иногда вдруг выдает вместо страницы сообщение «Документ не содержит данных». Браузеру все «до лампочки», что видно хотя бы по лампочкам приема у модема: страницу он скачал, но не отобразил. Мистика, на выяснение обстоятельств которой я однажды потратил целый день.

Добавьте сообщение, а затем нажмите Обновить в браузере — все сработает, как и должно!..

На этой оптимистической ноте позвольте закончить наблу. Хотя нет, давайте помечтаем о том времени, когда разработчики протокола HTTP догадаются ввести специальный заголовок, который будет действовать на кнопку Обновить точно так же, как и самопереадресация. А именно, в присутствии этого заголовка браузер вместо POST-запроса обновления должен посылать запрос GET по тому же самому URL. Конечно, только в том случае, если предыдущий POST-запрос сработал корректно (то есть, например, не произошло разрыва связи). Звучит заманчиво, не правда ли?..

 
Рекламный блок
   

На странице:
    17. Использование перенаправлений,  или внутренний и внешний редиректы, а также самопереадресация
Внешний редирект
Внутренний редирект
Самопереадресация

Важное объявление:
    автор категорически против копирования и распространения в Интернете всех статей «Куроводства» с возрастом, меньшим 6 месяцев. Печальный опыт «расползания» чрезвычайно устаревших ошибочных версий статьи про Apache действительно объясняет такое решение.

Орфография на «Куроводстве»:
    если вы заметили орфографическую, стилистическую или другую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Выделенный текст будет немедленно отослан вебмастеру, а Вы даже ничего и не заметите — настолько быстро все произойдет.

На заметку:
    если вы уже вскипели насчет дизайна этой страницы, то присмотритесь повнимательнее к названию, почитайте FAQ, сходите по лебедевским местам, как это уже предлагалось выше. Можно ли считать пародию плагиатом? Надеюсь, что нет.

Параметры этой страницы
   
GZip

Ссылки от спонсоров
    Самая свежая информация брокер на бирже москва на нашем сайте.


Дмитрий Котеров | 28 февраля 2002 г. ©1999-2016 | Генеральный спонсор: Хостинг «Джино» | Контакт Вернуться к оглавлению