Редиректы

Кирилл Евсеев, June 10, 2011

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

В общем, на сегодня наша задача разобраться с тем, как работает редирект, посмотреть примеры и разобраться в разнице подходов.
Мы рассмотрим следующие типы редиректа в PHP/HTML

– Стандартная функция header. Редирект с помощью отправки заголовков Location и Refresh
– Разница между 301 и 302 кодами состояния HTTP-протокола
– Редирект с помощью мета тега.
– Попытаемся ввести браузер в ступор ссылками на перекрёстные файлы и посмотрим, что будет.
Итак, стандартная функция header предназначена для отправки любых заголовков браузеру пользователя. Среди всего многообразия заголовков существует в т.ч. и заголовок Location, который указывает серверу URL, на который мы хотим перейти. Надо сразу же отметить, что мы можем вполне нормально указать имя файла, для перехода по локальному проекту.

Например, создайте три файла с таким содержимым –

//test.php
<?
  header("location: test3.php");
  exit;
?>

//test2.php
<?
  echo 'test 2';
  exit;
?>

//test3.php
<?
  header("location: http://localhost/test2.php");
  exit;
?>

Запустив на выполнение первый и третий тест, отлично видно, что результат одинаковый – редирект на файл test2.php.
Конечно же, функция header вполне способна принимать URL стороннего ресурса. Давайте посмотрим на заголовки, которые отсылает сервер при попытке запустить, например, test.php (для краткости я привожу только те заголовки, которые нас интересуют, игнорируя описание HTTP протокола, User-Agent и прочее)

Сначала запрос файла test.php:

GET /test.php HTTP/1.1
Host: localhost

В следующей строке сервер переводит протокол HTTP в состояние с кодом 302, обозначая, что запрашивается другой ресурс и этот ресурс найден.

HTTP/1.1 302 Found
Date: Tue, 07 Jun 2011 15:50:08 GMT
Server: Apache/2.2.19 (Win32) PHP/5.2.5

В следующей строке отправляется заголовок Location, который и говорит серверу, какой файл мы обрабатываем следующим.

Location: http://localhost/test2.php

Следующая строка показывает, сколько контента мы отдаём новому файлу. Т.к. мы не отдаём ничего, то результат – 0. На самом же деле, мы могли бы передать по всем правилам формирования URLа любые GET параметры, номер порта, якорь и т.п.

Content-Length: 0
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

Следующую строку сервер генерирует непосредственно в момент перехода на новый файл test2.php

GET /test2.php HTTP/1.1
Host: localhost

В следующей строке стандартный код состояния 200, обозначающий, что запрос ресурса произведён успешно.

HTTP/1.1 200 OK
Date: Tue, 07 Jun 2011 15:50:08 GMT
Server: Apache/2.2.19 (Win32) PHP/5.2.5

Заголовок Content-Length показывает, сколько контента отправляется в браузер пользователя в байтах.

Content-Length: 6
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
Content-Type: text/html

И в результате мы видим нашу надпись из файла test2.php

Заметьте, функция header отсылает заголовок. Если бы перед её вызовом вы попытались что-нибудь вывести в поток, например так –

<?
  echo 'redirect will be failed';
  header("location: test3.php");
  exit;
?>

либо так

<html><body>

<p>Все равно ничего не выйдет</p>

<?
header("location: test3.php");
exit;
?>

</body></html>

То вы получили бы ошибку типа Warning с сообщением о том, что редирект невозможет, т.к. заголовки уже отправлены в браузер. Даже пустой символ или символ пробела инициирует окончательную отправку заголовков браузеру и разрыв HTTP соединения. Фактически, PHP пытается попросить сервер о новых заголовках после того, как соединение разорвано, что и приводит к ошибке. Т.о., если вы хотите получить какой-либо полезный эффект от отправки заголовка Location, вы не можете отправлять другие заголовки, приводящие к разрыву соединения.

Редирект можно инициировать и другим способом. Отредактируем наш файл test.php и отправим другой заголовок – Refresh.

<?
  header("refresh:5; url=test2.php");
  exit;
?>

Как вы уже догадались, время обновления задаётся в секундах, точка с запятой – разделительный символ между параметрами, параметр URL содержит адрес страницы. Адрес может быть как текущий, так и любой другой. Вы наверняка видели много раз в интернете страницы (например, после авторизации или попытки скачать файл), с сообщением о том, что вы будете перемещены на другую страницу через несколько секунд, либо же, нажмите ссылку для перехода, если у вас не работает автоматическое перенаправление. Это достигается именно таким заголовком. Т.к. в случае заголовка refresh можно вполне без проблем отсылать любой контент и разрывать соединение. Фактически, происходит следующее – сервер отсылает браузеру набор данных и закрывает соединение. Браузер получает вместе с данными для отображения ещё и дополнительные инструкции, которые гласят – через определённое время сэмулируешь нажатие кнопки Refresh и загрузишь указанный адрес.

Так что следующее вполне допустимо –

<?
  echo 'Waiting 5 seconds';
  header("refresh:5; url=test2.php");
  exit;
?>

Посмотрим на заголовки для чистоты эксперимента

Перешли по адресу http://localhost/test.php

GET /test.php HTTP/1.1
Host: localhost
Keep-Alive: 115
Connection: keep-alive

Состояние 200, разрыв соединения, браузер показывает контент, если он есть.

HTTP/1.1 200 OK
Date: Tue, 07 Jun 2011 16:11:39 GMT
Server: Apache/2.2.19 (Win32) PHP/5.2.5
X-Powered-By: PHP/5.2.5
refresh: 5;url=test2.php
Content-Length: 0
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

Новый переход через 5 секунд на http://localhost/test2.php

GET /test2.php HTTP/1.1
Host: localhost:81
Keep-Alive: 115
Connection: keep-alive

Разрываем соединение и показываем контент из второго файла.

HTTP/1.1 200 OK
Date: Tue, 07 Jun 2011 16:11:44 GMT
Server: Apache/2.2.19 (Win32) PHP/5.2.5
X-Powered-By: PHP/5.2.5
Content-Length: 2
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

Во всей этой идиллии есть один подводный камень. А именно, состояние протокола с кодом 302 (отправка хедера Location). Все дело в том, что код 302 обозначает временное перемещение файла по указанному адресу. Все бы ничего, но поисковые системы обычно подобные штуки не индексируют. Поэтому, если вы вдруг решите построить на редиректах архитектуру сайта, вы можете неприятно удивится, когда обнаружите, что индексируется только главная страница, а все остальные остаются в полной неизвестности для поисковых систем. Решение есть. Стандартная функция header принимает ещё 2 необязательных параметра. Второй параметр – replace по умолчанию равен true и предотвращает повторную отправку заголовков одного и того же типа. Если второй параметр установить равным false, то появится возможность отправить несколько одинаковых хедеров в одном запросе. Правда, с Location это, скорее всего, не сработает. В общем, нас сильнее интересует третий параметр. Третий параметр функции header позволяет устанавливать вручную код состояния протокола. И вот тут кроется самое важное. Мы можем вручную установить не 302й код, а 301й. Смотрим пример –

Код файла test.php

<?
  header("location: test2.php", false, 301);
  exit;
?>

С логической точки зрения ничего не изменилось, мы перешли по редиректу на test2.php, однако, протокол получил 301й код состояния, что означает, что по этому адресу содержится новый файл, который будет там находится постоянно, а не временно, как при коде 302. Поисковые системы услышав слово постоянно (т.е. получив код 301), сразу занервничают и обновят свои базы. Т.о., если вы решили построить архитектуру с помощью редиректов, вы сможете быть уверены, что все страницы вашего сайта будут успешно проиндексированы.

Ещё один способ сделать редирект – это мета-теги. Отредактируем файл test.php следующим образом

<html>
<head>
<meta http-equiv="refresh" content="5; url=test2.php">
</head>

</html>

С точки зрения отправки заголовков, такой рефреш будет на 100% аналогичен вызову header(“refresh:5; url=test2.php”);
Но в этот раз мы обошлись без PHP. Внутренний голос мне подсказывает, что с мета-тегами можно спокойно работать в DOM-модели, а, значит, у вас появляется возможность писать программы, использующие редирект, на клиентской стороне без привлечения серверных скриптов.

Ну что ж, попробуем теперь немножко потестировать браузер на предмет крепких нервов.

//test.php
<?
header("location: test2.php", false, 301);
?>

//test2.php -
<?
header("location: test.php", false, 301);
?>

Не буду приводить хедеры, т.к. их оказалось очень много 🙂 Фаерфокс боролся аж 40 переходов (по 20 с каждого файла), пока не выдал ошибку. вот последний набор хедеров, который получил браузер

HTTP/1.1 301 Moved Permanently
Date: Tue, 07 Jun 2011 16:43:53 GMT
Server: Apache/2.2.19 (Win32) PHP/5.2.5
X-Powered-By: PHP/5.2.5
Location: test2.php
Content-Length: 0
Keep-Alive: timeout=5, max=80
Connection: Keep-Alive
Content-Type: text/html
X-Pad: avoid browser bug

Вот она, истина в вине, – X-Pad: avoid browser bug

Осталось разобрать последний вопрос. Вы наверняка заметили, что я вызывал оператор exit во всех PHP скриптах. Этот оператор немедленно прерывает работу скрипта. Если я отправляю заголовок Location, значит, я точно отсюда ухожу. я вызываю exit на всякий случай. По идее, хороший сервер должен перейти и прервать дальнейшую работу скрипта. Однако, если этого не произойдёт, скрипт продолжит работу и образуется утечка памяти. Веб – среда многопользовательская. Утечка от одного юзера, от другого… глядишь и повисли. Чтобы этого не произошло, я вызываю exit на всякий случай и рекомендую вам делать так же.

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

В тему:

0комментариев
Ваше имя
Ваш email*
Ваш сайт
Текст вашего комментария:

Поиск по блогу:
Подписаться:
Популярные:
Облако тегов:
Разное:
Счетчик: