По работе мне потребовалось использовать удалённое API для получения информации об IP клиентов. В PHP я нашёл два варианта как можно выполнить запрос на удалённый сервер: Sockets и cURL. Так как с cURL я был уже знаком, решил узнать что такое Sockets.
Насколько я понял - Сокет это просто точка соединения. Есть разные типы Сокетов, наиболее распространённые (и реализованные в PHP) это AF_INET для интернет-соединений и AF_UNIX для коммуникаций между процессами ОС.
Это мой первый опыть работы с Sockets, поэтому наверняка есть какие-то недочёты в моём решении. Плюс к этому - моя задача, на данный момент, ограничена работой с HTTP-запросами: отправить Get или Post, и считать ответ.
Первым делом, я взял один из примеров с документации по PHP и начал работу с него. Принцип работы с этим модулем следующий:
// создание сокета
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
// подключение созданного сокета
socket_connect($socket, $address, $service_port);
// запись в сокет запроса
socket_write($socket, $in, strlen($in));
// считывание ответа фиксированными порциями
while ($out = socket_read($socket, 2048))
{
}
// закрытие
socket_close($socket);
Основная сложность в том, что запрос нужно выполнять с помощью HTTP-заголовка и ответ также приходит с HTTP заголовком, который нужно сначала выделить а потом разобрать нужные поля.
Необходимую информацию о заголовках я нашёл тут:
Необходимую информацию о заголовках я нашёл тут:
, и официальный ресурс (которым я почти не воспользовался):
У меня получился следующий класс:
if (!defined('NL'))
{
define('NL', "\r\n");
define('DNL', NL . NL);
}
class HttpSocket
{
private $socket;
private $host_str;
private $query_str;
private $ip_addr;
private $resp_code;
public function __construct()
{
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($this->socket === false)
trigger_error('socket_create() failed: reason: ' . socket_strerror(socket_last_error()), E_USER_ERROR);
}
public function reqGet($addr)
{
$this->parseAddr($addr);
$header = 'GET ' . $this->query_str . ' HTTP/1.1' . NL;
$header .= 'Host: ' . $this->host_str . NL;
$header .= 'Connection: Close' . DNL;
return $this->doQuery($header);
}
public function reqPost($addr, $var_arr = array())
{
$this->parseAddr($addr);
$req_str = http_build_query($var_arr);
$header = 'POST ' . $this->query_str . ' HTTP/1.1' . NL;
$header .= 'Host: ' . $this->host_str . NL;
$header .= 'Content-Type: application/x-www-form-urlencoded' . NL;
$header .= 'Content-Length: ' . strlen($req_str) . NL;
$header .= 'Connection: Close' . DNL;
$header .= $req_str;
return $this->doQuery($header);
}
public function getRespCode()
{
return $this->resp_code;
}
public function __destruct()
{
socket_close($this->socket);
}
private function parseAddr($addr)
{
// http://hostname.com/query/string?v=1
// http://192.168.241.125/query/string?v=1
preg_match('/^(?:https?:\/\/)?([^\/]+)(.*)/', $addr, $matches);
if (filter_var($matches[1], FILTER_VALIDATE_IP))
{
$this->ip_addr = $matches[1];
} else {
$this->host_str = $matches[1];
$this->ip_addr = gethostbyname($this->host_str);
}
$this->query_str = strlen($matches[2]) ? $matches[2] : '/';
}
private function doQuery($header)
{
$service_port = getservbyname('www', 'tcp');
$result = socket_connect($this->socket, $this->ip_addr, $service_port);
if ($result === false)
trigger_error('socket_connect() failed. Reason: (' . $result . ') ' . socket_strerror(socket_last_error($this->socket)), E_USER_ERROR);
socket_write($this->socket, $header, strlen($header));
$resp_str = $this->doFetch();
$resp_bdy = $this->parseResponse($resp_str);
return $resp_bdy;
}
private function doFetch()
{
$resp = '';
while ($out = socket_read($this->socket, 2048))
$resp .= $out;
return $resp;
}
private function parseResponse($resp_str)
{
$pos_cr = strpos($resp_str, "\n\n");
$pos_crlf = strpos($resp_str, "\r\n\r\n");
if ($pos_cr !== false AND $pos_cr < $pos_crlf)
{
$sep_pos = $pos_cr;
$sep_len = 2;
} else {
$sep_pos = $pos_crlf;
$sep_len = 4;
}
$head = substr($resp_str, 0, $sep_pos);
preg_match('/HTTP[^\s]+\s(\d+)/i', $head, $matches);
$this->resp_code = $matches[1];
preg_match('/Content-Length:\s(\d+)/i', $head, $matches);
$length = intval($matches[1]);
$body = substr($resp_str, $sep_pos + $sep_len, $length);
return $body;
}
}
Функции __construct() и __destruct() соответственно создают и закрывают сокет. Функции reqGet() и reqPost() получают URL в качестве аргумента и выполняют соответствующие запросы.
Пару слов о HTTP заголовках:
- Строка "Host: ..." сообщает на какой именно домен идёт запрос (на случай если на одном IP несколько доменов).
- Строка "Connection: Close" сообщает что соединение не должно считаться постоянным.
Ещё немного напишу о процессе разбора ответа в функции parseResponse():
- Сначала нужно отделить заголовок от основного сообщения, для этого ищу двойную новую строку. Это может быть как "\r\n\r\n", так и просто "\n\n".
- Далее сохраняю код ответа и выбираю информацию о длинне сообщения (Content-Length).
После этого уже выделяю само сообщение.
Комментариев нет:
Отправить комментарий