Обработка тайм-аутов Soap в PHP

Я работаю над проектом, где я проверяю информацию от пользователя с помощью веб-службы SOAP. В настоящее время я занимаюсь ошибками, предполагая, что получаю ответы от веб-службы, но также должен обрабатывать крайние случаи таймаута службы или недоступности.

В случае тайм-аута или недоступности службы мне нужно сделать вид, что запрос был успешным (что веб-служба одобрила информацию), но я не понимаю, какие исключения выбрасываются.

Некоторые псевдокоды:

// $client is PHP's SoapClient class try { $response = $client->SomeSoapRequest(); } catch(SoapFault $e){ // handle issues returned by the web service } catch(Exception $e){ // handle PHP issues with the request } 

Кажется, я не могу найти:

  1. Являются ли тайм-ауты SoapFault ? Если да, то каков наилучший способ различать ошибку таймаута и проблемы веб-сервиса (например, ошибка типа и т. Д.)? Я нашел одну страницу, в которой упоминалась ошибка, когда сообщение было чем-то вроде «Ошибка загрузки заголовков», но не упоминало, было ли это ошибкой мыла.
  2. Как потенциально может случиться отсутствие службы? Исключение PHP похоже, что это имеет смысл (SoapFault будет возвращен из веб-службы, где недоступность будет проблемой сокета или аналогичной)?
  3. Существует ли существующая служба (например, пример), с которой я могу протестировать таймаут? Большинство обсуждений, связанных с таймаутом, похоже, связаны с предотвращением тайм-аутов, расширяя настройку таймаута по умолчанию, что не является идеальным в этой ситуации.

1) В случае таймаута PHP выдает исключение SoapFault с faultcode="HTTP" и faultstring="Error Fetching http headers" .

2) На мой взгляд, лучший способ различать ошибку тайм-аута и проблемы веб-службы – это посмотреть на faultcode faultstring faultcode и faultstring класса SoapFault .
В частности, элемент faultcode предназначен для использования программным обеспечением для обеспечения алгоритмического механизма идентификации неисправности.
Как вы также можете прочитать в комментарии руководства PHP , нет способа прочитать свойство faultcode , поэтому вам нужно получить к нему доступ напрямую (например, $e->faultcode ), потому что метод getCode() не работает.
SOAP 1.1 Spec определяет четыре возможных значения для поля faultcode :

  • VersionMismatch : сторона обработки обнаружила недопустимое пространство имен для элемента SOAP Envelope
  • MustUnderstand : непосредственный дочерний элемент элемента заголовка SOAP, который либо не был понят, либо не соблюден для обрабатывающей стороны, содержал атрибут SOAP mustUnderstand со значением «1»,
  • Клиент . Класс ошибок клиента указывает на то, что сообщение было неправильно сформировано или не содержало соответствующей информации для успеха. Например, сообщение может не иметь надлежащей информации об аутентификации или платеже. Как правило, это сообщение о том, что сообщение не должно повторяться без изменений.
  • Сервер . Класс ошибок сервера указывает, что сообщение не может быть обработано по причинам, не связанным напрямую с содержимым самого сообщения, а скорее с обработкой сообщения. Например, обработка может включать в себя связь с процессором восходящего потока, который не отвечал. Сообщение может преуспеть в более поздний момент времени.

В дополнение к этим кодам PHP использует HTTP код для идентификации ошибок, происходящих на уровне протокола (например: ошибки сокета); например, если вы add_soap_fault в исходном коде ext / soap / php_http.c, вы можете увидеть, когда генерируются некоторые из этих типов сбоев.
При поиске add_soap_fault и soap_server_fault в исходных файлах расширения PHP SOAP я построил следующий список исключений PHP SoapFault :

 HTTP ---- Unable to parse URL Unknown protocol. Only http and https are allowed. SSL support is not available in this build Could not connect to host Failed Sending HTTP SOAP request Failed to create stream?? Error Fetching http headers Error Fetching http body: No Content-Length: connection closed or chunked data Redirection limit reached: aborting Didn't recieve an xml document Unknown Content-Encoding Can't uncompress compressed response Error build soap request VersionMismatch --------------- Wrong Version Client ------ A SOAP 1.2 envelope can contain only Header and Body A SOAP Body element cannot have non Namespace qualified attributes A SOAP Envelope element cannot have non Namespace qualified attributes A SOAP Header element cannot have non Namespace qualified attributes Bad Request Body must be present in a SOAP envelope Can't find response data DTD are not supported by SOAP encodingStyle cannot be specified on the Body encodingStyle cannot be specified on the Envelope encodingStyle cannot be specified on the Header Error cannot find parameter Error could not find "location" property Error finding "uri" property looks like we got "Body" with several functions call looks like we got "Body" without function call looks like we got no XML document looks like we got XML without "Envelope" element Missing parameter mustUnderstand value is not boolean SoapClient::__doRequest() failed SoapClient::__doRequest() returned non string value Unknown Data Encoding Style Unknown Error DataEncodingUnknown MustUnderstand -------------- Header not understood Server ------ Couldn't find WSDL DTD are not supported by SOAP Unknown SOAP version WSDL generation is not supported yet 

3) Чтобы смоделировать условие таймаута, попробуйте выполнить следующий код:

soapclient.php

 <?php ini_set('default_socket_timeout', 10); $client = new SoapClient(null, array( 'location' => "http://localhost/soapserver.php", 'uri' => "http://localhost/soapserver.php", 'trace' => 1 ) ); try { echo $return = $client->__soapCall("add",array(41, 51)); } catch (SoapFault $e) { echo "<pre>SoapFault: ".print_r($e, true)."</pre>\n"; //echo "<pre>faultcode: '".$e->faultcode."'</pre>"; //echo "<pre>faultstring: '".$e->getMessage()."'</pre>"; } ?> 

soapserver.php

 <?php function add($a, $b) { return $a + $b; } sleep(20); $soap = new SoapServer(null, array('uri' => 'http://localhost/soapserver.php')); $soap->addFunction("add"); $soap->handle(); ?> 

Обратите внимание на вызов sleep в сценарии SoapServer.php с временем (20), которое больше времени (10), указанного для параметра default_socket_timeout в сценарии SoapClient.php .
Если вы хотите имитировать недоступность службы, вы можете, например, изменить протокол location с http на https в сценарии soapclient.php , считая, что ваш веб-сервер не настроен для SSL; делая это, PHP должен выбросить «Не удалось подключиться к хосту» SoapFault.

Похоже, что default_socket_timeout не учитывается при вызове SOAP через HTTPS:

  • Ошибка # 48524 – Настройка таймаута не учитывается при вызовах SOAP + HTTPS .

Ошибка открыта на момент написания. В комментариях к сообщению в блоге Роберт Людвик упоминается в удаленном ответе. Timing Out PHP Soap Calls (21 октября 2009 г., опубликовано Робертом Ф. Людвиком) указывает, что обходной вопрос, обсуждаемый в статье (переопределение SoapClient::__doRequest() с помощью curl request) также работает над этой ошибкой.

Другая связанная ошибка:

  • Ошибка # 41631 – default_socket_timeout не работает с SSL

Код, упомянутый в сообщении в блоге, претерпел некоторые изменения и может быть найден в его последней форме с поддержкой HTTP-аутентификации здесь, на Github:

  • SoapClientTimeout.class.php

В любом случае обходной путь больше не понадобится, поскольку эта проблема исправлена ​​в расширении PHP SOAPClient.

Чтобы справиться с таймаутами в сервисе

 $client = new SoapClient($wsdl, array("connection_timeout"=>10)); // SET SOCKET TIMEOUT if(defined('RESPONSE_TIMEOUT') && RESPONSE_TIMEOUT != '') { ini_set('default_socket_timeout', RESPONSE_TIMEOUT); } 

По моему опыту, если $e->getMessage – «Ошибка получения заголовков HTTP», вы имеете дело с сетевым таймаутом.

Если $e->getMessage – это что-то вроде «Не удается подключиться к хосту», служба, которую вы пытаетесь достичь, не работает.

Тогда есть «Похоже, что у нас нет XML-документа», который более загадочный, может означать разные вещи.

Я использовал два фактора, чтобы получить расширение SoapClient, чтобы создать отличное исключение. Сообщение и время, которое запрос потребовал вернуть. Я думаю, что сообщение об ошибке «Ошибка получения заголовков http» также может возникать в некоторых других случаях, поэтому проверка времени.

Следующий код должен быть прав

 class SoapClientWithTimeout extends SoapClient { public function __soapCall ($params, ---) { $time_start = microtime(true); try { $result = parent::__soapCall ($params, ---); } catch (Exception $e) { $time_request = (microtime(true)-$time_start); if( $e->getMessage() == 'Error Fetching http headers' && ini_get('default_socket_timeout') < $time_request ) { throw new SoapTimeoutException( 'Soap request most likly timed out.'. ' It took '.$time_request. ' and the limit is '.ini_get('default_socket_timeout') ); } // E: Not a timeout, let's rethrow the original exception throw $e; } // All good, no exception from the service or PHP return $result; } } class SoapTimeoutException extends Exception {} 

Затем я использую SoapClientWithTimeout

 $client = new SoapClientWithTimeout(); try { $response = $client->SomeSoapRequest(); var_dump($response); } catch(SoapTimeoutException $e){ echo 'We experienced a timeout! '. $e->getMessage(); } catch(Exception $e) { echo 'Exception: '.$e->getMessage(); } 

Отладка времени обслуживания. Добавьте следующую строку перед вызовом службы

 ini_set('default_socket_timeout', 1); 

Просто установка default_socket_timeout глобально через ini может не делать то, что вы хотите. Это повлияет на запросы SOAP, но также повлияет на другие исходящие соединения, включая соединения с БД. Вместо этого переопределите метод __doRequest () SoapClient, чтобы сделать HTTP-соединение самостоятельно. Затем вы можете установить свой тайм-аут в сокете, обнаружить его и выбросить исключения, которые вы можете захватить и обработать.

 class SoapClientWithTimeout 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 to add a timeout. */ public function __doRequest ( $request, $location, $action, $version, $one_way = 0 ) { // Extract host, port, and scheme. $url_parts = parse_url ($location); $host = $url_parts['host']; $port = @$url_parts['port'] ?: ($url_parts['scheme'] == 'https' ? 443 : 80); $length = strlen ($request); // Form the 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; // Need to tell fsockopen to use SSL when requested. if ($url_parts['scheme'] == 'https') $host = 'ssl://'.$host; // Open the connection. $socket = @fsockopen ( $host, $port, $errno, $errstr, $this->_connectionTimeout ); if (!$socket) throw new SoapFault ( 'Client', "Failed to connect to SOAP server ($location): $errstr" ); // Send the request. stream_set_timeout ($socket, $this->_socketTimeout); fwrite ($socket, $http_req); // Read 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" ); // 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; } } 

Думаю, я немного опоздал, но в случае, если кто-то все еще ищет решение тайм-аутов в клиенте php soap – вот что сработало для меня:

В основном заменив PHP SoapClient на cURL с установленным таймаутом. Просто имейте в виду, иногда WS ожидает действия, указанные в HTTP-заголовке. Исходное решение, размещенное на этом веб-сайте, не включает это (проверьте комментарии).

просто используйте «stream_context», чтобы установить параметр таймаута также для загрузки WSDL (вам нужно установить параметры SoapClient $ ['connection_timeout'] раньше):

 class SoapClient2 extends SoapClient { public function __construct($wsdl, $options=null) { if(isset($options['connection_timeout'])) { $s_options = array( 'http' => array( 'timeout' => $options['connection_timeout'] ) ); $options['stream_context'] = stream_context_create($s_options); } parent::__construct($wsdl, $options); } }