<?php
/*
SHCMS_http является клиентом класса для протокола HTTP.
Так же как и получение информации с сервера, могут взаимодействовать SHCMS_http
с сервером через POST или GET. Поэтому он может быть использован как часть любой
скрипт, который должен взаимодействовать с приложением, работающим на другом сайте.
Он поддерживает основные аутентификации и куки (и таким образом сессий).
*/
class SHCMS_http {
//Запрос переменные:
protected $host, $port, $path;
protected $method;
protected $http_link;
protected $postdata = '';
protected $cookies = array();
protected $referer;
protected $accept = 'text/xml,application/xml,application/xhtml+xml,text/html,text/plain,image/png,image/jpeg,image/gif,*/*';
protected $accept_encoding = 'gzip';
protected $accept_language = 'ru-RU';
protected $user_agent = 'SHCMS_agent';
//Опции:
protected $timeout = 20;
protected $use_gzip = true;
protected $persist_cookies = true;
protected $persist_referers = true;
protected $debug = false;
protected $handle_redirects = true;
protected $max_redirects = 5;
protected $headers_only = false;
//Основные переменные разрешения:
protected $username, $password;
//Ответ переменные:
protected $status;
protected $headers = array();
protected $content = '';
protected $errormsg;
protected $redirect_count = 0;
//Конструкторов / деструкторов:
public function __construct($link,$host, $port=80) {
/*
$host: веб-сервера
$port: дополнительный номер порта
$link: Обработка ссылки
*/
$this->http_link = $link;
$this->host = $host;
$this->port = $port;
}
public function __destruct() {
foreach ($this as $index => $value) unset($this->$index);
}
public function __toString() {
return $this->getContent();
}
//Запрос методов выполнения:
public function get($path, $data = null) {
/*
Выполняет GET запрос для указанного пути.
Возвращает в случае успеха и в случае возникновения ошибки. Если ложь, ошибка
сообщение с описанием возникшей проблемы могут быть доступны
использованием GetError () метод.
$data: дополнительно - если указано, добавляет его в строку запроса как часть
запрос получите. $data могут быть массивом пар ключ-значение, в
этом случае соответствующий строке запроса будет построен.
*/
$this->path = $path;
$this->method = 'GET';
if ($data) $this->path .= '?'.http_build_query($data);
return $this->doRequest();
}
public function post($path, $data) {
/*
Выполняет POST запрос по указанному пути, отправки информации
указанные в $ данных.
Возвращает в случае успешного завершения или в случае возникновения ошибки. Если ложь, ошибка
сообщение с описанием возникшей проблемы могут быть доступны
использованием GetError () метод.
$data: дополнительно - массив пар ключ-значение, и в этом случае соответствия
Разместить спрос будет построен.
*/
$this->path = $path;
$this->method = 'POST';
$this->postdata = http_build_query($data);
$result = $this->doRequest();
$this->postdata = null;
}
public function ok() {
// Используйте это после get() или post(), чтобы проверить состояние последнего запроса.
// Возвращает истину, если статус был 200 OK - в противном случае возвращает ложь.
return ($this->status == 200);
}
//Ответ доступа:
public function getContent() {
// Возвращает содержимое HTTP ответ. Это, как правило HTML документа.
return $this->content;
}
public function getStatus() {
// Возвращает код состояния ответа - 200 означает, ОК, 404 средств файл не найден и т.д.
return $this->status;
}
public function getHeaders() {
//Возвращает HTTP заголовки возвращаемые сервером как ассоциативный массив.
return $this->headers;
}
public function getName() {
$Files = @parse_url($this->http_link);
return basename($Files['path']);
}
public function getHeader($header) {
// Возвращает указанный в заголовке ответа, или ложным, если он не существует.
$header = strtolower($header);
if (isset($this->headers[$header])) {
return $this->headers[$header];
} else {
return false;
}
}
public function getError() {
//Возвращает строку с описанием самой последней ошибки.
return $this->errormsg;
}
public function getCookies($host = null) {
return @$this->cookies[$host ? $host : $this->host];
}
public function getRequestURL() {
// Возвращает полный URL, который был запрошен.
$url = 'http://'.$this->host;
if ($this->port != 80) {
$url .= ':'.$this->port;
}
$url .= $this->path;
return $url;
}
//Конфигурация методов:
public function setUserAgent($string) {
//Задает строку агента пользователя для использования в запросе.
// По умолчанию "SHCMS_agent".
$this->user_agent = $string;
}
public function setAuthorization($username, $password) {
// Устанавливает HTTP имя пользователя и пароль разрешения для использования в запросах.
// Внимание: не забудьте сбросить этот в последующие запросы на другие сервера!
$this->username = $username;
$this->password = $password;
}
public function setCookies($array, $replace = false) {
if ($replace || !is_array(@$this->cookies[$this->host]))
$this->cookies[$this->host] = array();
$this->cookies[$this->host] = ( $array + $this->cookies[$this->host] );
}
public function useGzip($boolean) {
// Укажите, если клиент должен запросить GZIP закодированное содержимое с сервера -
// это экономит пропускную способность, но может увеличить процессорного времени. По умолчанию включено.
$this->use_gzip = $boolean;
}
public function setPersistCookies($boolean) {
/*
Укажите, если клиент должен сохраняться печенья между запросами.
По умолчанию включено.
Примечание: Это в данный момент игнорируют печенья пути (и времени) полностью.
Время не важно, но путь мог привести к проблемам безопасности.
*/
$this->persist_cookies = $boolean;
}
public function setPersistReferers($boolean) {
// Укажите, если клиент должен использовать URL из предыдущего запроса как
// Направление последующего запроса. По умолчанию включено.
$this->persist_referers = $boolean;
}
public function setHandleRedirects($boolean) {
// Укажите, если клиент должен автоматически следовать перенаправлены запросы.
// По умолчанию включено.
$this->handle_redirects = $boolean;
}
public function setMaxRedirects($num) {
// Установить максимальное число перенаправлений допускается до клиента
// сдается (в основном для предотвращения бесконечных циклов). По умолчанию 5 переадресацию.
$this->max_redirects = $num;
}
public function setHeadersOnly($boolean) {
// Если включен, клиент будет извлекать только заголовки со страницы.
// Это может быть полезно для реализации вещей, как ссылка шашки.
// По умолчанию отключена.
$this->headers_only = $boolean;
}
public function setDebug($boolean) {
// Включает отладочные сообщения в HTML выходе от клиента.
// По умолчанию отключена.
$this->debug = $boolean;
}
//Статические методы утилиты:
public static function quickGet($url, $data = null) {
/*
Статический метод ярлык для быстрого создания и настройки нового
Экземпляр этого класса, а также выполнять GET запрос.
Благодаря строку-магии, вы можете использовать значения, возвращаемого этой
метод непосредственно в эхо заявление, если хотите.
*/
$bits = parse_url($url);
$host = $bits['host'];
$port = isset($bits['port']) ? $bits['port'] : 80;
$path = isset($bits['path']) ? $bits['path'] : '/';
if (isset($bits['query']))
$path .= '?'.$bits['query'];
$client = new SHCMS_http($host, $port);
$client->get($path, $data);
return $client;
}
public static function quickPost($url, $data) {
// Как и в [SHCMS_http::quickGet ()], но выполняет POST запрос.
$bits = parse_url($url);
$host = $bits['host'];
$port = isset($bits['port']) ? $bits['port'] : 80;
$path = isset($bits['path']) ? $bits['path'] : '/';
$client = new HttpClient($host, $port);
$client->post($path, $data);
return $client;
}
//Внутренние вспомогательные методы:
protected function debug($msg, $object = false) {
/*
Отображает внутреннее сообщение для отладки и устранения неполадок,
Если включена отладка.
Использование [SHCMS_http::setDebug ()], чтобы включить отладку.
*/
if ($this->debug) {
echo '<div style="border: 1px solid red; padding: 0.5em; margin: 0.5em;"><strong>SHCMS_http Debug:</strong> ' . $msg;
if ($object)
echo '<pre>' . htmlspecialchars(print_r($object,true)) . '</pre>';
echo '</div>';
}
}
protected function doRequest() {
// Выполняет действительное запроса HTTP, возвращает истину в случае успеха, ложно в случае ошибки.
if (!$fp = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout)) {
//Установить сообщение об ошибке:
switch($errno) {
case -3:
$this->errormsg = 'Создание сокета не удалось (-3)';
case -4:
$this->errormsg = 'Сбой поиска DNS(-4)';
case -5:
$this->errormsg = 'Отказ в соединении или тайм-аут (-5)';
default:
$this->errormsg = 'Сбой подключения ('.$errno.')';
$this->errormsg .= ' '.$errstr;
$this->debug($this->errormsg);
}
return false;
}
socket_set_timeout($fp, $this->timeout);
$request = $this->buildRequest();
$this->debug('Request', $request);
fwrite($fp, $request);
//Сброс всех переменных которые не должны существовать между запросами:
$this->headers = array();
$this->content = '';
$this->errormsg = '';
//Установка пару флагов:
$inHeaders = true;
$atStart = true;
//Теперь начать читать обратно ответ:
while (!feof($fp)) {
$line = fgets($fp, 4096);
if ($atStart) {
//Сделка с первой строки возвращаемые данные:
$atStart = false;
if (!preg_match('/HTTP\/(\\d\\.\\d)\\s*(\\d+)\\s*(.*)/', $line, $m)) {
$this->errormsg = "Код состояния линии недействительными: ".htmlentities($line);
$this->debug($this->errormsg);
return false;
}
$http_version = $m[1]; //Не используется
$this->status = $m[2];
$status_string = $m[3]; //Не используется
$this->debug(trim($line));
continue;
}
if ($inHeaders) {
if (trim($line) == '') {
$inHeaders = false;
$this->debug('Полученные заголовки', $this->headers);
if ($this->headers_only) {
break; //Пропустите оставшуюся часть входного
}
continue;
}
if (!preg_match('/([^:]+):\\s*(.*)/', $line, $m)) {
//Переход к следующему заголовку:
continue;
}
$key = strtolower(trim($m[1]));
$val = trim($m[2]);
//Сделка с возможностью несколько заголовков с таким же именем:
if (isset($this->headers[$key])) {
if (is_array($this->headers[$key])) {
$this->headers[$key][] = $val;
} else {
$this->headers[$key] = array($this->headers[$key], $val);
}
} else {
$this->headers[$key] = $val;
}
continue;
}
//Мы не в заголовках, так что добавить строку с содержанием:
$this->content .= $line;
}
fclose($fp);
//Если данные сжимаются, распаковать его:
if (isset($this->headers['content-encoding']) && $this->headers['content-encoding'] == 'gzip') {
$this->debug('Содержимое GZIP закодированы, распаковать ее');
$this->content = substr($this->content, 10);
$this->content = gzinflate($this->content);
}
//Если $persist_cookies, иметь дело с любыми печенья:
if ($this->persist_cookies && isset($this->headers['set-cookie'])) {
$cookies = $this->headers['set-cookie'];
if (!is_array($cookies))
$cookies = array($cookies);
if (!is_array(@$this->cookies[$this->host]))
$this->cookies[$this->host] = array();
foreach ($cookies as $cookie) {
if (preg_match('/([^=]+)=([^;]+);/', $cookie, $m)) {
$this->cookies[$this->host][$m[1]] = $m[2];
}
}
}
//Если $persist_referers, установите Referer готово для следующего запроса:
if ($this->persist_referers) {
$this->debug('Сохранение Referer: '.$this->getRequestURL());
$this->referer = $this->getRequestURL();
}
//Наконец, если handle_redirects и перенаправления отправляется, сделайте это:
if ($this->handle_redirects) {
if (++$this->redirect_count >= $this->max_redirects) {
$this->errormsg = 'Количество перенаправления превышало предельно ('.$this->max_redirects.')';
$this->debug($this->errormsg);
$this->redirect_count = 0;
return false;
}
$location = isset($this->headers['location']) ? $this->headers['location'] : '';
$location .= isset($this->headers['uri']) ? $this->headers['uri'] : '';
if ($location) {
$this->debug("После перенаправления на: $location" . (@$url['host'] ? ", host: ".$url['host'] : ''));
$url = parse_url($location);
if (@$url['host']) $this->host = $url['host'];
return $this->get(($url['path']{0} == '/' ? '' : '/') . $url['path']);
}
}
return true;
}
protected function buildRequest() {
//Создает заголовки запроса HTTP.
$headers = array();
$headers[] = "{$this->method} {$this->path} HTTP/1.0"; // * Использование 1,1 приводит к всяким проблемам, таким как "поблочного" кодирования
$headers[] = "Host: {$this->host}";
$headers[] = "User-Agent: {$this->user_agent}";
$headers[] = "Accept: {$this->accept}";
if ($this->use_gzip)
$headers[] = "Accept-encoding: {$this->accept_encoding}";
$headers[] = "Accept-language: {$this->accept_language}";
if ($this->referer)
$headers[] = "Referer: {$this->referer}";
// * Cookies:
if (@$this->cookies[$this->host]) {
$cookie = 'Cookie: ';
foreach ($this->cookies[$this->host] as $key => $value) {
$cookie .= "$key=$value; ";
}
$headers[] = $cookie;
}
// * Обычная проверка подлинности:
if ($this->username && $this->password)
$headers[] = 'Authorization: BASIC '.base64_encode($this->username.':'.$this->password);
// * Если это POST, установить тип содержания и объема:
if ($this->postdata) {
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
$headers[] = 'Content-Length: '.strlen($this->postdata);
}
$request = implode("\r\n", $headers)."\r\n\r\n".$this->postdata;
return $request;
}
}
?>