Обработка таймаутов мыла в PHP - PullRequest
32 голосов
/ 07 мая 2009

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

Ответы [ 8 ]

32 голосов
/ 12 января 2013

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
  • 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

<code><?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)."
\ п "; // echo "
faultcode: '".$e->faultcode."'
"; // echo "
faultstring: '".$e->getMessage()."'
"; } ?>

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 «Не удалось подключиться к хосту».

7 голосов
/ 08 сентября 2010

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

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

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


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

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

3 голосов
/ 07 мая 2009

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

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

// SET SOCKET TIMEOUT
if(defined('RESPONSE_TIMEOUT') &&  RESPONSE_TIMEOUT != '') {
 ini_set('default_socket_timeout', RESPONSE_TIMEOUT);
}
2 голосов
/ 20 декабря 2010

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

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

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

1 голос
/ 28 апреля 2012

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

просто используйте «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);
  }
}
0 голосов
/ 20 февраля 2015

Простая настройка 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;
    }

}
0 голосов
/ 26 февраля 2013

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...