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

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

Вобъемном проекте часто трудно проконтролировать все возможные ситуации, и рано или поздно отображение информации в окно браузера осуществляется перед выполнением функции 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])

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

Если определен необязательный параметр $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 состоит из трех полей:

Для реализации базовой аутентификации браузеру клиента необходимо послать следующие 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>

В окне веб-браузера это будет выглядеть ТАК.




  • Другие |
назадвверхвперед
Rambler's Top100