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

Справочный материал по основным языкам программирования и верстки сайтов.

Готовая методика создания простых и сложных динамичных сайтов, с использованием PHP и MySQL.

Использование веб-редактора Adobe Dreamweaver в разработке сайтов.

Использование графических редакторов Adobe Flash, Adobe Photoshop, Adobe Fireworks в подготовке веб-графики.

Разработка веб-сайтов под "ключ".

Разработка отдельных фрагментов сайтов, консультации по вопросам верстки веб-страниц и веб-программирования.

Буферизированный вывод. Размер и тип документа. Подавление кэширования. Базовая аутентификация

Базовая аутентификация

Буферизированный вывод

В объемном проекте часто трудно проконтролировать все возможные ситуации, и рано или поздно отображение информации в окно браузера осуществляется перед выполнением функции header(), что приводит к выводу предупреждения: Warning: Cannot modify header information - headers already sent by. Такая ситуация может возникнуть например, при наличии случайного пробела или какой-либо ошибки, которая ни разу не возникала в процессе тестирования. Не стоит и говорить, что вывод подобного предупреждения искажает дизайн сайта и выглядит непрофессионально. Конечно можно подавить вывод предупреждения, предварив функцию header() символом @ или отключить вывод сообщений в окно браузера при помощи функции error_reporting(0), однако это лишь скроет предупреждение и не заставит сработать функцию header().

Выходом из ситуации является буферизация вывода, при котором весь объем данных, выводимых в окно браузера, помещается в буфер и отправляется клиенту тогда, когда скрипт завершит работу. Это позволяет безболезненно выполнять все функции header() в теле скрипта, где бы они не находились.

Самый простой способ установить буферизацию вывода — воспользоваться директивой output_buffering конфигурационного файла php.ini, установив ее значение равной on.

Включение буферизации при помощи директивы output_buffering

. . .
output_buffering = On
. . .

Однако настройки конфигурационного файла php.ini могут отличаться на разных серверах, поэтому при переносе приложения не стоит расчитывать на то, что директива output_buffering окажется включенной (тем более, что по умолчанию она принимает значение Off — отключена).

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

Функции управления выводом
Таблица. Функции управления выводом
Функция Описание
ob_clean() Очищает буфер вывода
ob_end_clean() Очищает буфер вывода и отключает буферизацию вывода
ob_end_flush() Отправляет буфер вывода клиенту и отключает буферизацию вывода
ob_flush() Отправляет буфер вывода клиенту
ob_get_clean() Возвращает текущее содержание буфера и удаляет текущий буфер
ob_get_contents() Возвращает содержимое буфера вывода
ob_get_flush() Очищает буфер, возвращая его содержимое в виде строки, и отключает буферизацию
ob_get_length() Возвращает размер буфера вывода
ob_get_level() Возвращает уровень вложения буфера
ob_get_status([$full_status]) Возвращает статус буфера вывода в виде массива; если необязательный параметр $full_status принимает значение true, возвращается более подробная информация.
ob_gzhandler($buffer, $mode) Функция обратного вызова, которая используется функцией ob_start() для задания обработчика сжатия данных
ob_implicit_flush([$flag]) Включает (если значение параметра $flag устанавливается в 1) или выключает (если значение $flag устанавливается в 0) неявную очистку буфера
ob_list_handlers() Возвращает список всех используемых обработчиков буферизации
ob_start([$output_callback
[, $chunk_size [, $erase]]])
Включает буферизацию вывода; в качестве параметра $output_callback может принимать название функции обратного вызова, которую задает обработчик. Необязательный параметр $chunk_size задает количество байт буфера, через которые будет осуществляться повторный вызов функции $output_callback. Если необязательный параметр $erase принимает значение false, то буфер не будет удаляться после завершения работы скрипта
output_add_rewrite_var
($name, $value)
Добавляет к URL, обнаруженным в буфере вывода, дополнительный GET-параметр с именем $name и значением $value
output_reset_rewrite_vars() Уничтожает дополнительные GET-параметры, добавленные функцией output_add_rewrite_var()

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

Буферизация вывода
<?php
//Весь вывод направляем в буфер
ob_start();
//Выводим содержимое страницы
echo "Hello world!";
//Отправляем HTTP-заголовок
header("X-my-header: Hello world!");
//Отправляем содержимое буфера вывода клиенту
ob_end_flush();
?>

Размер и тип документа

При загрузке объемного файла сервер обычно отправляет размер документа в байтах при помощи HTTP-заголовка Content-length. Это позволяет оценить объем загружаемого документа (файла), а также время, необходимое для его загрузки. Следует помнить, сто веб-сервер способен вычислить только размер статического файла, например, изображения HTML-страницы или zip-архива. Для динамических файлов, таких как РНР-скрипт, отправка размера документа при помощи HTTP-заголовка Content-length ложится на плечи разработчика. Однако часто определить размер документа можно только после того, как скрипт завершит свою работу и клиенту отправлена значительная часть HTTP-документа. В такой ситуации удобно воспользоваться функциями управления выводом.

Рассмотрим пример, где скрипт, формирующий объемный текстовой файл, размер которого вычисляется при помощи функции ob_get_length() и отправляется клиенту при помощи HTTP-заголовка Content-length.

Формирование HTTP-заголовка Content-length
<?php
//весь вывод отправляем в буфер
ob_start();
//выводи содержимое страницы
for($i=0; $i<300000; $i++) echo "<pre>01\r\n";
//отправляем клиенту размер страницы
//в HTTP-заголовке Content-length
header("Content-length: ".ob_get_length());
//отправляем содержимое буфера клиенту
ob_end_flush();
?>

При загрузке скрипта веб-сервер и браузер интерпретируют его содержимое как HTML-документ, который выводится в окно браузера. Поскольку HTTP-заголовок Content-type, устанавливающий тип документа отсутствует, сервер назначает ему значение text/html, и браузер интерпретирует такой документ по умолчанию как HTML-страницу. Однако такая интерпретация не всегда удобна: для объемных файлов было бы разумнее предложить пользователю сохранить его на жесткий диск, а для этого необходимо изменить файл.

Изменение типа файла
<?php
//весь вывод отправляем в буфер
ob_start();
//выводи содержимое страницы
for($i=0; $i<300000; $i++) echo "01\r\n";
//задаем имя, которое будет предложено клиенту
//для сохранения файла
header("Content-Disposition: attachment; filename=text.txt");
//в качестве типа файла задаем бинарный поток
header("Contenr-type: application/octet-stream");
//от правляем клиенту размер страницы
//в HTTP-заголовке Content-length
header("Content-length: ".ob_get_length());
//отправляем содержимое буфера клиенту
ob_end_flush();
?>

В качестве типа документа в HTTP-заголовке Contenr-type указывается значение application/octet-stream, соответствующее бинарному потоку. Заголовок Content-Disposition позволяет задать имя файла, в данном случае text.txt. В результате загрузки страницы со скриптом на экран посетителя выводится окно.

Загрузка файла

При этом если будет случайно или умышленно пропущен HTTP-заголовок, то размер загружаемого файла не отразится.

Загрузка файла

Подавление кэширования

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

Кэши принято подразделять на два вида: локальные и глобальные. Локальный кэш создается браузером клиента, тогда как глобальный располагается на прокси-сервере провайдера (или организации, в которой имеется свой внутренний прокси-сервер).

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

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

Для подавления кэширования можно использовать HTTP-заголовки.

Примечание. К сожалению, в последнее время кэширующие прокси-серверы настраиваются крайне агрессивно. Дело в том, что динамические страницы, созданные например, при помощи РНР, вообще не должны подвергаться кэшировнию. Однако сплошь ирядом наблюдается обратное. Более того, ряд кэширующих серверов игнорируют HTTP-заголовки, подавляющие кэширование. В этом случае разработчику ничего не остается делать, как добавлять к URL GET-параметр со случайно сгенерируемым значением.

Подавление кэширования
<?php
//любая дата в прошлом
header ("Expires: Mon, 23 May 2008 02:00:00 GMT");
header ("Last-Modified: " .gmdate(D, d M Y H:i:s"). "GMT";
header ("
Cache-Control: no-cache, must-revalidate");
header ("
Pragma: no-cache");
. . .
?>

HTTP-заголовок Expires задает дату, по достижении которой документ считается устаревшим, поэтому задание для этого загаловка уже прошедшей даты предотвращает кэширование данной страницы.

HTTP-заголовок Last-Modified определяет дату последнего изменения веб-страницы. Если с момента последнего обращения к ней прокси-сервера значение параметра этого заголовка изменилось, происходит повторная загрузка страницы, поэтому присвоение этой директиве текущего времени при каждом обращении предотвращает кэширование.

HTTP-заголовок Cache-Control предназначен для управления кэшированием, и указание его значения равным no-cache также приводит к запрету кэширования. В устаревшем стандарте HTTP 1.0 для запрета кэширования нужно присвоить значение no-cache HTTP-заголовку Pragma.

Для того, чтобы сообщить промежуточному прокси-серверу о том, что данный документ можно кэшировать, HTTP-заголовку Cache-Control следует передать значение public. Если информация не предназначена для публичных кэш-серверов и может быть сохранена только в локальном кэше браузера, HTTP-заголовку Cache-Control следует передать значение private.

Кэширование документов
<?php
header ("Cache-Control: public");
header ("Cache-Control: private");
. . .
?>

Если содержимое страницы обновляется с определенной регулярностью, задать период обновления можно путем передачи HTTP-заголовку Cache-Control параметра max-age. Данный параметр определяет количество секунд, определяющее время жизни копии страницы в кэше. В листинге скрипт сообщает, что страница может храниться на промежуточном прокси-сервере 1 час.

Управление временем кэширования документа
<?php
header ("Cache-Control: public");
header ("Cache-Control: max-age=3600");
. . .
?>

Для управления кэшем в РНР предусмотрено две специальные функции: session_cache_limiter() и session_cache_expire().

Примечание. В РНР достаточно много функций, которые берут на себя формирование и отправку HTTP-заголовков (session_cache_limiter(), session_cache_expire(), setcookie(), session_start() и т.д.). При отсутствии буферизации использование таких функций после вывода какой-либо информации в окно браузера приводит к появлению предупреждения: Warning: Cannot modify header information - headers already sent by

Функция session_cache_limiter() возвращает и/или устанавливае ограничение кэширования и имеет следующий синтаксис:

string session_cache_limiter([$cache_limiter])

Данная функция возвращает одну из следующих строк, определяющих ограничение кэширования:

  • none — отсутствие каких либо ограничений, HTTP-заголовок Cache-Control не отправляется;
  • nocache — подавление кэширования;
  • private — HTTP-заголовок Cache-Control получает значение "private", соответствующее кэшированию на уровне браузера посетителя;
  • private_no_exoire — режим аналогичный private, за исключение того, что клиенту не отсылается HTTP-заголовок Expires, который может вызывать неоднозначность в браузерах, построенных на движке Mozilla;
  • public — HTTP-заголовок Cache-Control получает значение "public", соответствующее кэшированию на уровне прокси-серверов.
  • Если определен необязательный параметр $cache_limiter, то текущее значение ограничителя заменяется на новое.
  • Использование функции session_cache_limiter()
    <?php
    //получаем текущее значение ограничения
    $cache_limiter_fst=session_cache_limiter();
    //устанавливаем ограничение кэша в "public"
    session_cache_limiter("public");
    //получаем текущее значение ограничителя
    $cache_limiter_snd=session_cache_limiter();
    //выводим отчет
    echo "Ограничение на кэш изменено ". " с $cache_limiter_fst на $cache_limiter_snd";
    ?>

    Функция session_cache_expire() возвращает и/или устанавливает текущее время жизни HTTP-документа в кэше и имеет следующий вид:

    int session_cache_expire([$new_cache_expire])

    Необязательный параметр $new_cache_expire определяет время жизни документа в кэше в минутах (по умолчанию этот параметр равен 180). Если вызов функции session_cache_expire() осуществляется с параметром $new_cache_expire, то текущее время жизни (180 минут) заменяются на значение, переданные в этом параметре.

    Использование функции session_cache_expire()
    <?php
    //устанавливаем врямя жизни кэша, равное 30 минутам
    $cache_expire=session_cache_expire(30*60);
    echo $cache_expire;
    ?>

    Базовая аутентификация

    Веб-сервер позволяет защитить страницу при помощи базовой аутентификации — пока пользователь не введет правильные логин и пароль, он не будет допущен к страницам сайта.

    Обычно такой вид аутентификации задается при помощи конфигурационных файлов .htaccess и .htpasswd, однако это не всегда удобно, т.к. добавление нового пользователя требует редактирования файла паролей .htpasswd.

    Создадим систему базовой аутентификации, логин и пароль для которой, будет храниться в таблице userslist.

    Таблица userslist

    Таблица userslist

    Каждая запись таблицы userslist состоит из трех полей:

    • id_user — первичный ключ таблицы, обладающий атрибутом AUTO_INCREMENT;
    • name — имя пользователя;
    • pass — пароль пользователя.

    Для реализации базовой аутентификации браузеру клиента необходимо послать следующие HTTP-заголовки:

    WWW-Authenticate: Basic realm = "Admin Page"
    HTTP/1.0 401 Unauthorized
    Именно они приводят к выводу формы для вывода логина и пароля.

    Подключение к серверу

    Имя пользователя будет помещено сервером в элемент суперглобального массива $_SERVER['PHP_AUTH_USER'], а пароль в $SERVER['PHP_AUTH_PW'].

    Примечание. Элементы суперглобального массива $_SERVER['PHP_AUTH_USER'] и $_SERVER['PHP_AUTH_PW'] доступны только в том случае, если РНР установлен в качестве модуля, а не CGI-приложения.

    Для удобства код аутентификации удобно выделить в отдельный файл security_mod.php, включение которого при помощи директивы require_once() будет приводить к защите страницы паролем.

    Файл security_mod.php
    <?php
    //устанавливаем соединение с базой данных
    require_once("config.php");
    //если пользователь не авторизовался - авторизуемся
    if(!isset($_SERVER['PHP_AUTH_USER']))
    {
    header("WWW-Authenticate: Basic realm = \"Admin Page\"");
    header("HTTP/1.0 401 Unauthorized");
    exit();
    }
    else
    {
    //проверяем переменные $_SERVER['PHP_AUTH_USER']
    //и $SERVER['PHP_AUTH_PW'], чтобы предотвратить
    //SQL-инъекцию
    if(!get_magic_quotes_gpc())
    {
    $_SERVER['PHP_AUTH_USER']= mysql_escape_string($_SERVER['PHP_AUTH_USER']);
    $_SERVER['PHP_AUTH_PW']= mysql_escape_string($_SERVER['PHP_AUTH_PW']);
    }
    $query="SELECT pass FROM userslist WHERE name='{$_SERVER[PHP_AUTH_USER]}'";
    $lst=@mysql_query($query);
    //если найдена ошибка в SQL-запросе-
    //открываем диалоговое окно ввода пароля
    if (!$lst)
    {
    header("WWW-Authenticate: Basic realm = \"Admin Page\"");
    header("HTTP/1.0 401 Unauthorized");
    exit();
    }
    //если такого пользователя нет-
    //открываем диалоговое окно ввода пароля
    if(mysql_num_rows($lst)==0)
    {
    header("WWW-Authenticate: Basic realm = \"Admin Page\"");
    header("HTTP/1.0 401 Unauthorized");
    exit();
    }
    //если все проверки пройдены, сравниваем кэши паролей
    $pass=@mysql_fetch_array($lst);
    if(md5($_SERVER['PHP_AUTH_PW'])!=$pass['pass'])
    {
    header("WWW-Authenticate: Basic realm = \"Admin Page\"");
    header("HTTP/1.0 401 Unauthorized");
    exit();
    }
    }
    ?>

    Как видно из листинга, при неудачной авторизации клиенту отправляется повторное приглашение для ввода пароля:

    WWW-Authenticate: Basic realm = "Admin Page"
    HTTP/1.0 401 Unauthorized

    Далее работа скрипта останавливается при помощи функции exit(). Дополнительно можно реализовать ограничение количества попыток ввода пароля. Для этого достаточно вместо приведенных выше HTTP-заголовков послать заголовок "Страница не найдена":

    HTTP/1.0 404 Not Found

    После того, как модуль защиты security_mod.php создан, его можно включить перед загрузкой защищаемых страниц при помощи директивы require_once().

    <?php
    //Модуль безопасности
    require_once("security_mod.php");
    ?>
    //Далее идут данные страницы, к которой нужно получить доступ

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
    <title>Untitled Document</title>
    </head><body>
    <h2>Привет всем!</h2>
    </body>
    </html>