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

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

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

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

// $client is PHP 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
}

Я не могу найти:

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

Ответ 1

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

2) На мой взгляд, лучший способ различать ошибку тайм-аута и проблемы веб-сервиса - это посмотреть на 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 script с временем (20), самым длинным, чем время (10), указанное для параметра default_socket_timeout в SoapClient.php script.
Если вы хотите имитировать недоступность службы, вы можете, например, изменить протокол location от HTTP до https в SoapClient.php script, считая, что ваш веб-сервер не настроен для SSL; делая это, PHP должен выбросить "Не удалось подключиться к хосту" SoapFault.

Ответ 2

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

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

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


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

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

Ответ 3

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

$client = new SoapClient($wsdl, array("connection_timeout"=>10));

// SET SOCKET TIMEOUT
if(defined('RESPONSE_TIMEOUT') &&  RESPONSE_TIMEOUT != '') {
 ini_set('default_socket_timeout', RESPONSE_TIMEOUT);
}

Ответ 4

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

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

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

Ответ 5

Я использовал два фактора, чтобы получить расширение 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 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);

Ответ 6

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

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

Ответ 7

Просто установка default_socket_timeout во всем мире через ini может не делать то, что вы хотите. Это повлияет на запросы SOAP, но также повлияет на другие исходящие соединения, включая соединения с БД. Вместо этого переопределите метод SoapClient __doRequest(), чтобы сделать 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;
    }

}

Ответ 8

просто используйте "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);
  }
}