Анализ потока данных SOAP в реальном времени с помощью PHP
У меня есть корпоративное приложение, которое предоставляет довольно надежный API для сбора данных. В настоящее время я запрашиваю каждого пользователя, которого я хочу обновлять в цикле каждую секунду. Однако новая документация по API теперь предоставляет прямую трансляцию всех изменений для всех пользователей. Мне интересно, как я могу проанализировать эти текущие данные, поскольку они поставляются с php. Вот некоторые подробности:
Данные запрашиваются с помощью запроса SOAP, и я использую PHP для выполнения таких запросов (пример сеанса инициализация с возвращенным уникальным идентификатором):
//Get a session ID for this user for the shoretel WEBAPI
$soap_url = 'http://11.11.11.11:8070/ShoreTelWebSDK?wsdl';
$client = new SOAPClient($soap_url, array( 'proxy_host' => '11.11.11.11', 'proxy_port' => 8070, 'trace' => 1 ) );
$client = new SoapClient($soap_url);
$header = new SoapHeader('http://www.ShoreTel.com/ProServices/SDK/Web');
$client->__setSoapHeaders($header);
$registered_string = $client->RegisterClient(array(
'clientName' => '11.11.11.211'
));
$registered_string = get_object_vars($registered_string);
$session = $registered_string['RegisterClientResult'];
Новый метод позволяет мне указать период времени. Например, если я хотел бы получить все события за 1 минуту, вызов начнется, подождите одну минуту, а затем верните все события, которые произошли в течение этой минуты.
Что я хотел бы сделать, так это захватить каждое событие по мере его возникновения и вставить его в базу данных. Это то, чего я могу достичь с помощью PHP, или мне нужно искать другой язык, чтобы это произошло?
5 answers
Хорошо, цель состоит в том, чтобы "транслировать ответ на МЫЛО" с "таймаутом" и/или в "интервале"?
Я предлагаю переопределить метод SoapClient __doRequest()
и реализовать пользовательское соединение через fsockopen()
и передавать данные с помощью stream_get_contents()
.
Теперь вы получаете поток XML-данных, и то, что вам нужно, находится в его середине.
Вам придется извлечь XML-конверт или его части, возможно, с помощью строковых функций или с помощью preg_match
для извлечения внутреннего содержимого.
Следующий код обеспечивает выход SoapClientTimeout класс, где тайм-аут устанавливается с помощью stream_set_timeout()
. Это для случая использования, когда сервер медленно реагирует, и вы хотите убедиться, когда закончится прослушивание.
Я предлагаю поиграть с ним и настроить поведение тайм-аута в сокете. Потому что вы хотите прекратить прослушивание через некоторое время (интервальная выборка). Итак, вы пытаетесь совместить тайм-аут с блокировкой, чтобы через некоторое время прекратить чтение из потока:
$timeout = 60; // seconds
stream_set_blocking($socket, true);
stream_set_timeout($socket, $timeout);
Когда у вас есть поток, который блокируется через 1 минуту и закрывается, вам нужен цикл (с твердым условием выхода), запускающий следующий запрос.
class SoapClientTimeout extends SoapClient
{
public function __construct ($wsdl, $options = null)
{
if (!$options) $options = [];
$this->_connectionTimeout = @$options['connection_timeout'] ?: ini_get ('default_socket_timeout');
$this->_socketTimeout = @$options['socket_timeout'] ?: ini_get ('default_socket_timeout');
unset ($options['socket_timeout']);
parent::__construct($wsdl, $options);
}
/**
* Override parent __doRequest and add "timeout" functionality.
*/
public function __doRequest ($request, $location, $action, $version, $one_way = 0)
{
// fetch host, port, and scheme from url.
$url_parts = parse_url($location);
$host = $url_parts['host'];
$port = @$url_parts['port'] ?: ($url_parts['scheme'] == 'https' ? 443 : 80);
$length = strlen ($request);
// create HTTP SOAP request.
$http_req = "POST $location HTTP/1.0\r\n";
$http_req .= "Host: $host\r\n";
$http_req .= "SoapAction: $action\r\n";
$http_req .= "Content-Type: text/xml; charset=utf-8\r\n";
$http_req .= "Content-Length: $length\r\n";
$http_req .= "\r\n";
$http_req .= $request;
// switch to SSL, when requested
if ($url_parts['scheme'] == 'https') $host = 'ssl://'.$host;
// connect
$socket = @fsockopen($host, $port, $errno, $errstr, $this->_connectionTimeout);
if (!$socket) {
throw new SoapFault('Client',"Failed to connect to SOAP server ($location): $errstr");
}
// send request with socket timeout
stream_set_timeout($socket, $this->_socketTimeout);
fwrite ($socket, $http_req);
// start reading the response.
$http_response = stream_get_contents($socket);
// close the socket and throw an exception if we timed out.
$info = stream_get_meta_data($socket);
fclose ($socket);
if ($info['timed_out']) {
throw new SoapFault ('Client', "HTTP timeout contacting $location");
}
// the stream contains XML data
// lets extract the XML from the HTTP response and return it.
$response = preg_replace (
'/
\A # Start of string
.*? # Match any number of characters (as few as possible)
^ # Start of line
\r # Carriage Return
$ # End of line
/smx',
'', $http_response
);
return $response;
}
}
Привязать сокет
Функция bind может использоваться для привязки сокета к определенному адресу и порту. Для этого нужна структура sockaddr_in, аналогичная функции connect.
Краткий пример
if(!($sock = socket_create(AF_INET, SOCK_STREAM, 0)))
{
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("Couldn't create socket: [$errorcode] $errormsg \n");
}
echo "Socket created \n";
// Bind the source address
if( !socket_bind($sock, "127.0.0.1" , 5000) )
{
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("Could not bind socket : [$errorcode] $errormsg \n");
}
echo "Socket bind OK \n";
Теперь, когда привязка завершена, пришло время заставить сокет прослушивать соединения. Мы привязываем сокет к определенному IP-адресу и определенному номеру порта. Делая это, мы гарантируем, что все входящие данные, которые направляются на этот номер порта, принимаются этим приложение.
Это делает очевидным, что у вас не может быть 2 сокета, привязанных к одному и тому же порту. Из этого правила есть исключения, но мы рассмотрим это в какой-нибудь другой статье.
Прослушивайте соединения
После привязки сокета к порту следующее, что нам нужно сделать, это прослушать соединения. Для этого нам нужно перевести сокет в режим прослушивания. Функция socket_listen
используется для перевода сокета в режим прослушивания. Просто добавьте следующую строку после связывать.
//listen
socket_listen ($sock , 10)
Второй параметр функции socket_listen называется невыполненной работой. Он контролирует количество входящих подключений, которые остаются "ожидающими", если программа уже занята. Таким образом, указание 10 означает, что если 10 соединений уже ожидают обработки, то 11-й запрос на подключение должен быть отклонен. Это станет более понятным после проверки socket_accept.
Теперь начинается основная часть принятия новых подключений.
Принять соединение
Для этого используется функция socket_accept
.
if(!($sock = socket_create(AF_INET, SOCK_STREAM, 0)))
{
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("Couldn't create socket: [$errorcode] $errormsg \n");
}
echo "Socket created \n";
// Bind the source address
if( !socket_bind($sock, "127.0.0.1" , 5000) )
{
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("Could not bind socket : [$errorcode] $errormsg \n");
}
echo "Socket bind OK \n";
if(!socket_listen ($sock , 10))
{
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("Could not listen on socket : [$errorcode] $errormsg \n");
}
echo "Socket listen OK \n";
echo "Waiting for incoming connections... \n";
//Accept incoming connection - This is a blocking call
$client = socket_accept($sock);
//display information about the client who is connected
if(socket_getpeername($client , $address , $port))
{
echo "Client $address : $port is now connected to us.";
}
socket_close($client);
socket_close($sock);
Выход
Запустите программу. Это должно показать $php /var/www/server.php Созданный сокет Привязка сокета в порядке Сокет слушает нормально Ожидание входящих подключений...
Итак, теперь эта программа ожидает входящих подключений на порту 5000. Не закрывайте эту программу, продолжайте ее запускать. Теперь клиент может подключиться к нему через этот порт. Мы будем использовать клиент telnet для тестирования этого. Откройте терминал и введите $телнет локальный хост 5000
Это сразу же покажет $telnet локальный хост 5000 Пытаюсь 127.0.0.1... Подключен к локальному хосту. Экранирующий символ - "^]". Соединение закрыто иностранным хостом.
И вывод сервера покажет Клиент 127.0.0.1: 36689 теперь подключен к нам.
Таким образом, мы можем видеть, что клиент подключен к серверу. Попробуйте выполнить описанные выше действия, пока не добьетесь идеальной работы.
Примечание
Функция socket_getpeername используется для получения подробной информации о клиент, который подключен к серверу через определенный сокет.
Я немного озадачен: SOAP (будучи простым протоколом доступа к объектам) полностью противоположен потоковой передаче в реальном времени. Каждое сообщение SOAP полностью инкапсулировано: у него есть начало и конец. Это просто невозможно передать в потоковом режиме.
Вы упомянули какой-то "новый метод", но не сообщили никаких подробностей. "Живые" обновления могут быть достигнуты с помощью повторяющихся запросов или это может быть фактический поток данных. Если это первое: вы можете уйти с МЫЛОМ, но звонить МЫЛУ каждую секунду - не очень хорошая идея: администратор удаленного сервера не будет доволен таким натиском. Если это последнее: это не может быть МЫЛО. Период.
Если вы не разъясните, что делает "новый метод" и как он вызывается, никто не сможет сказать вам, как решить вашу проблему. Но будьте уверены, PHP в наши дни способен практически на все. Поэтому с какой бы проблемой вы ни столкнулись, вам, безусловно, не нужно переходить на какой-либо другой язык программирования, если вам удобно работать с PHP. Я предлагаю вам изменить свой первоначальный вопрос и добавить соответствующие подробности. Я уверен, что вы найдете здесь множество людей, которые смогут дать вам точное решение.
Пара вещей с вашим кодом:
- Вы создаете экземпляр $client дважды, поэтому первый $client эффективно перезаписывается.
- Ваш заголовок SOAP имеет только аргумент пространства имен. Это может и не потребоваться.
Похоже, что ваш код регистрирует только идентификатор сеанса. Неясно, где находятся данные, может быть, в другом элементе массива $registered_string? Я подозреваю, что после этого вам нужно написать еще больше кода.
Обычно, по моему опыту, вы бы тогда использовали возвращенный идентификатор сеанса для создания URL-адреса. Затем вы будете использовать этот URL-адрес для доступа к потоку данных. Это будет НЕ вызов SOAP, а обычный веб-запрос с использованием любого метода, который вы предпочитаете (например, file_get_contents('http://example.com/blah?session=[SESSIONID]')
). Сервер будет использовать идентификатор сеанса для аутентификации пользователя перед возвратом данных.
Если это так работает, то ответ - да, вы можете получить доступ к данным и вставить их в базу данных, но только пока действителен идентификатор сеанса. Когда истечет срок действия идентификатора сеанса, вам придется позвонить Снова зарегистрируйте client(). Это будет верно независимо от того, какой язык вы используете.
Простым способом может быть использование websockets или даже запрос ajax с таймаутом для вашего файла php soap.