Иногда РНР-скрипты выступают не только с сервера, но и со стороны клиента, обращаясь к удаленным страницам. Самый простой способ реализации такого обращения — воспользоваться файловыми функциями.
Загрузка страницы с удаленного хоста (вариант1)
<?php
//загружаем страницу с удаленного хоста
$content=file_get_contents("http://sevidi.narod.ru");
//сохраняем содержимое страницы в файле
$fd=fopen("url.txt", "w");
fwrite($fd, $content);
fclose($fd);
?>
В результате работы скрипта, содержимое индексной страницы сайта http://sevidi.narod.ru будет сохранено в файле url.txt. Функция file_get_contents() является достаточно удобной и возвращает содержимое локального или удаленного файла в виде строки. Функция появилась в РНР, только с версии 4.3.0. Для более ранних версий необходимо использовать связку функций fopen() и fgets().
Загрузка страницы с удаленного хоста (вариант2)
<?php
//получаем дескриптор удаленной страницы
$fd=fopen("http://sevidi.narod.ru", "r");
if(!$fd) exit("Запрашиваемая страница не найдена");
$content="";
//чтение содержимого файла в переменную text
while (!feof($fd))
{
$content.=fgets($fd, 1024);
}
//закрыть открытый указатель файла
fclose($fd);
//сохраняем содержимое страницы в файле
$fd=fopen("url.txt", "w");
fwrite($fd, $content);
fclose($fd);
?>
В данном случае загрузка страницы осуществляется при помощи функции fgets(), которая построчно извлекает из файла данные. Второй параметр указывает максимальное количество данных, считываемых за раз. Как правило, строка не превышает 1024 символа, поэтому данный параметр можно оставить без изменений. Разумеется, загрузка файлов во втором случае протекает медленнее, так как скрипту необходимо выполнить РНР-цикл, выполняемый интерпретатором, а не бинарный код функции file_get_contents(), созданный при помощи языка С. Однако второй подход имеет преимущества при загрузке больших файлов: дело в том, что для РНР-скрипта отводится строго ограниченный объем памяти, который задается в конфигурационном файле php.ini директивой memory_limit. Данная директива обычно принимает значение 8 или 16 Мбайт. Этот объем отводится как под сам РНР-скрипт, так и под все используемые в нем переменные и массивы. Таким образом, если разработчик имеет дело с файлом 10 Мбайт и содержимое этого файла будет полностью считано в переменную, над такой переменной невозможно будет выполнить даже операции замены, т.к. для этого потребуется 20 Мбайт. Подход используемый в последнем примере, предоставляет возможность обрабатывать файл построчно, не загружая полностью его содержимое в область памяти, отводимой для скрипта.
Файловые функции достаточно эффективны, однако удаленный сервер может определить, что к нему обращается не посетитель посредством браузера, а скрипт, потребовав установить cookies, проверить переменную окружения USER_AGENT, через которую клиент передает тип и версию браузера и операционной системы, и т.д. Разумеется, стандартные файловые системы не предоставляют такой информации — для полной эмуляции работы браузера необходимо воспользоваться либо сокетами, либо специальной библиотекой CURL.
Файловые функции не всегда удобны, т.к. удаленный хост может быть оптимизирован для работы с браузером, который должен поддерживать все особенности HTTP-протокола. В этом случае прибегают к сокетам. Подключение к удаленному серверу можно осуществить с помощью функции fsockopen(), которая имеет следующий синтаксис:
resouce fsockopen ($target, $port [, $errno [, $errstr [, $timeout]]])
Функция принимает пять параметров, первый из которых содержит адрес соединения, а второй номер порта. В случае удачи, функция возвращает дескриптор соединения $sock, в противном случае — значение false. Если при этом функции переданы два необязательных параметра $errno и errstr, в них размещается код и текстовое описание ошибки соответственно. Пятый, также необязательный параметр — значение тайм-аута, определяющего максимальное время ожидания ответа сервера в секундах. При успешной установке соединения функция возвращает дескриптор соединения, при неудаче — false.
Рассмотрим пример работы функции fsockopen(), где происходит загрузка главной страницы портала http://www.php.net/.
Пример использования функции fsockopen()
<?php
function get_content($hostname, $path)
{
$line="";
//устанавливаем соединение, имя которого
//передано в параметре $hostname
$fd=fsockopen($hostname, 80, $errno, $errstr, 30);
//проверяем успешность установки соединения
if(!fd) echo "$errstr ($errno)<br>/>\n";
else
{
//формируем HTTP-запрос для передачи его серверу
$headers="GET $path HTTP/1.1\r\n";
$headers.="Host: $hostname\r\n";
$headers.="Connection: Close\r\n\r\n";
//отправляем HTTP-запрос серверу
fwrite ($fd, $headers);
//получаем ответ
while (!feof($fd))
{
$line=fgets($fd, 1024);
}
fclose($fd);
}
return $line;
}
$hostname="www.php.net";
$path="/";
//устанавливаем большее время работы
//скрипта- пока вся страница не загружена,
//она не будет отображаться
set_time_limit(180);
//вызываем функцию
echo get_content($hostname, $path);
?>
В окне веб-браузера это будет выглядеть ТАК.
При работе с сокетами мы вынуждены брать на себя всю черновую работу по отправке серверу HTTP-запросов, получению о обработке ответов на них. В приведенном примере обращение к fsockopen() оформлено в виде функции get_content(), которая получает два параметра: $hostname — имя сервера, с которым устанавливается соединение, и $path — путь к странице относительно имени сервера.
Примечание. Следует помнить, что при работе с функцией fsockopen() имя сервера не должно содержать префикса http://, указывающего на тип протокола и стандартный порт 80. При работе с сокетами реализация протокола ложится на плечи программиста, а порт указывается во втором параметре функции fsockopen().
После установки соединения с сервером в переменной $headers формируется HTTP-запрос серверу по методу GET. При подстановке значений всех переменных серверу будут отправлены соответствующие заголовки.
Заголовки, отправляемые серверу www.php.net
GET/ HTTP/1.1\r\n
Host: www.php.net\r\n
Connection: Close\r\n\r\n
После отправки запроса функцией fgets() производится чтение ответа из сокета блоками по 1024 байта до тех пор, пока не встретится символ конца файла, определяемый функцией feof(). Затем результат возвращается и выводится в окно браузера.
Примечание. Из-за ошибки в реализации обращения через сокеты по протоколу HTTP/1.1 в операционной системе Windows выполняются медленно, поэтому в ней зачастую удобнее использовать протокол HTTP/1.0.
В первой строке у сервера запрашивается индексный файл корневого каталога сервера (/) методом GET по протоколу HTTP/1.1. HTTP-заголовок Host является обязательным. Он необходим HTTP-серверу для определения сайта, запрашиваемого клиентом (на тот случай, если к одному IP-адресу привязано несколько доменных имен). Последний HTTP-заголовок Connection требует от сервера закрыть соединение после передачи данных.
Примечание. Вместо (/) можно передать адрес страницы на сервере, например, /downloads.php, в результате чего будет загружена другая страница сайта — именно так и действуют браузеры. При переходе пользователей по ссылке они извлекают часть адреса после доменного имени и передают HTTP-запрос, подставив эту часть после ключевого слова GET.
Эти три строки эквивалентны запросу в окне браузера http://www.php.net/.
В начале страницы идут HTTP-заголовки, которые браузер обычно скрывает от пользователя. После чего начинается тело страницы, которое интерпретируется браузером. Избавиться от HTTP-заголовков можно при помощи функции strstr(), которая возвращает часть строки (первый параметр), начиная с первого вхождения подстроки (второй параметр).
Удаление HTTP-заголовков
<?php
function get_content($hostname, $path)
{
$line="";
//устанавливаем соединение, имя которого
//передано в параметре $hostname
$fd=fsockopen($hostname, 80, $errno, $errstr, 30);
//проверяем успешность установки соединения
if(!fd) echo "$errstr ($errno)<br>/>\n";
else
{
//формируем HTTP-запрос для передачи его серверу
$headers="GET $path HTTP/1.1\r\n";
$headers.="Host: $hostname\r\n";
$headers.="Connection: Close\r\n\r\n";
//отправляем HTTP-запрос серверу
fwrite ($fd, $headers);
//получаем ответ
while (!feof($fd))
{
$line.=fgets($fd, 1024);
}
fclose($fd);
}
return $line;
}
$hostname="www.php.net";
$path="/";
//устанавливаем большее время работы
//скрипта- пока вся страница не загружена,
//она не будет отображаться
set_time_limit(180);
//вызываем функцию
$content=get_content($hostname, $path);
echo strstr($content,'<');
?>
В окне веб-браузера это будет выглядеть ТАК.
Несмотря на то, что скрипт загружает страницу с портала http://www.php.net/, адресная строка указывает на скрипт, раположенный на локальном хосте. Таким образом в качестве браузера выступает РНР-скрипт.