Pregunta Manejo de tiempos de espera de Soap en PHP


Estoy trabajando en un proyecto en el que estoy verificando información de un usuario con un servicio web SOAP. Actualmente me estoy ocupando de los errores, suponiendo que recibo respuestas del servicio web, pero también tengo que manejar los casos extremos de un tiempo de espera o indisponibilidad del servicio.

En el caso de un tiempo de espera o falta de disponibilidad del servicio, debo pretender que la solicitud fue exitosa (que el servicio web aprobó la información), pero no tengo claro qué excepciones se lanzan.

Algunos pseudo-códigos:

// $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
}

Lo que no puedo encontrar es:

  1. Son tiempos de espera a SoapFault? De ser así, ¿cuál es la mejor manera de distinguir entre un error de tiempo de espera y problemas del servicio web (como un error de tipo, etc.)? Encontré una página que mencionaba un error en el que el mensaje decía algo así como "Error al cargar encabezados", pero no mencionaba si se trataba de un error de Soap.
  2. ¿Cómo va a ocurrir la falta de disponibilidad de un servicio? Una excepción de PHP parece que tendría sentido (un SoapFault se devolverá desde el servicio web donde la falta de disponibilidad sería un problema de socket o similar)?
  3. ¿Existe un servicio existente (por ejemplo, un ejemplo) contra el que puedo poner a prueba un tiempo de espera? La mayoría de las discusiones relacionadas con el tiempo de espera parecen estar relacionadas con la prevención de tiempos de espera extendiendo la configuración de tiempo de espera predeterminado, que no es ideal en esta situación.

32
2018-05-07 14:59


origen


Respuestas:


1) En caso de tiempo de espera, PHP lanza una excepción SoapFault con faultcode="HTTP" y faultstring="Error Fetching http headers".

2) En mi opinión, la mejor manera de distinguir entre un error de tiempo de espera y problemas del servicio web es mirando el faultcode y faultstring Los miembros de la Clase SoapFault.
En particular, el faultcode El elemento está destinado a ser utilizado por el software para proporcionar un mecanismo algorítmico para identificar el error.
Como también puedes leer en un comentario del manual de PHP, no hay un método para leer el faultcode propiedad, por lo que debe acceder directamente (p. ej. $e->faultcode), porque el getCode() método no funciona.
los Especificación SOAP 1.1 define cuatro valores posibles para el faultcode campo:

  • Versión no coincide: La parte procesadora encontró un espacio de nombres no válido para el elemento SOAP Envelope
  • Debe entender: Un elemento hijo inmediato del elemento encabezado SOAP que no fue entendido o no obedecido por la parte procesadora contenía un atributo SOAP mustUnderstand con un valor de "1"
  • Cliente: La clase de errores del Cliente indica que el mensaje se formó incorrectamente o no contenía la información adecuada para tener éxito. Por ejemplo, el mensaje podría carecer de la autenticación adecuada o la información de pago. En general, es una indicación de que el mensaje no debe reenviarse sin cambios.
  • Servidor: La clase de servidor de errores indica que el mensaje no pudo procesarse por razones que no son directamente atribuibles al contenido del mensaje en sí, sino al procesamiento del mensaje. Por ejemplo, el procesamiento podría incluir la comunicación con un procesador ascendente, que no respondió. El mensaje puede tener éxito en un momento posterior.

Además de esos códigos, PHP usa el HTTP código para identificar los errores que ocurren en el nivel de protocolo (p. ej .: errores de socket); por ejemplo, si busca add_soap_fault en el ext / soap / php_http.c código fuente que puede ver cuando se generan algunos de estos tipos de fallas.
Al buscar el add_soap_fault y soap_server_fault funciones en los archivos fuente de extensión SOAP de PHP, he creado la siguiente lista de PHP SoapFault excepciones:

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) Para simular la condición de tiempo de espera, intente con el siguiente código:

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();

?>

Observe la sleep llamar en el SoapServer.php script con un tiempo (20) más largo que el tiempo (10) especificado para el default_socket_timeout parámetro en el SoapClient.php guión.
Si desea simular una indisponibilidad del servicio, podría, por ejemplo, cambiar la location protocolo de http a https en el soapclient.php script, suponiendo que su servidor web no está configurado para SSL; Al hacer esto, PHP debería arrojar un SoapFault "Could not connect to host".


30
2018-01-11 22:18



Parece default_socket_timeout no se tiene en cuenta cuando se realizan llamadas SOAP a través de HTTPS:

Error abierto al momento de escribir. Como comentario en la publicación del blog Robert Ludwick a la que se hace referencia en una respuesta eliminada Cancelación de las llamadas de PHP Soap (21 de octubre de 2009, publicado por Robert F. Ludwick) señala, la solución discute la publicación (anulación SoapClient::__doRequest() con una solicitud curl) también funciona alrededor de este error.

Otro error relacionado es:


El código mencionado en la publicación del blog ha sufrido algunos cambios y se puede encontrar en su último formulario con soporte de autenticación HTTP aquí en Github:

En cualquier caso, la solución alternativa no debería ser necesaria ya que este problema se ha solucionado en la extensión PHP SOAPClient.


7
2017-09-08 19:41



Para lidiar con los tiempos de espera en el servicio

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

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

3
2018-05-07 16:53



De mi experiencia, si $e->getMessage es "Error al obtener encabezados http", se trata de un tiempo de espera de red.

Si $e->getMessage es algo así como "No se puede conectar con el host", el servicio que intentas alcanzar está inactivo.

Luego aparece "Parece que no tenemos ningún documento XML", que es más críptico y puede significar cosas diferentes.


2
2017-12-20 16:15



Usé dos factores para hacer que mi extensión de SoapClient lanzara una buena excepción. El mensaje y el tiempo que tomó la solicitud para regresar. Creo que el mensaje de error "Error al recuperar encabezados http" también puede aparecer en algunos otros casos, por lo tanto, la verificación de tiempo.

El siguiente código debería ser correcto

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 {}

Luego uso 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();
}

Para depurar el tiempo de espera de su servicio. Agregue la siguiente línea antes de llamar al servicio

ini_set('default_socket_timeout', 1);

1
2018-04-27 21:20



Simplemente configurando el default_socket_timeout globalmente a través de ini no puede hacer lo que quiera. Esto afectaría las solicitudes SOAP, pero también afectaría a otras conexiones salientes, incluidas las conexiones DB. En cambio, anule el método __doRequest () de SoapClient para hacer la conexión HTTP usted mismo. Luego puede establecer su propio tiempo de espera en el socket, detectarlo y lanzar excepciones que pueda atrapar y manejar.

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
2018-02-19 22:23



Supongo que llego un poco tarde, pero en caso de que alguien todavía esté buscando una solución a los tiempos de espera en el cliente de php soap, esto es lo que funcionó para mí:

Reemplazando Basic PHP SoapClient con cURL con set timeout. Solo tenga en cuenta que, a veces, WS espera que se especifique una acción en el encabezado HTTP. La solución original publicada en ese sitio web no incluye eso (ver comentarios).


0
2018-02-26 11:19



solo use el "stream_context" para establecer la configuración de tiempo de espera también para la carga de WSDL (debe configurar las opciones $ de SoapClient ['connection_timeout'] antes):

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
2018-04-22 12:32