consumir - soapclient php ejemplo
Manejo de tiempos de espera de Soap en PHP (8)
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:
- ¿Los tiempos de espera son una
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. - ¿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)?
- ¿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.
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 de servicio web es mirando los miembros de faultcode
y faultstring
de la clase SoapFault .
En particular, el elemento de faultcode
está destinado a ser utilizado por el software para proporcionar un mecanismo algorítmico para identificar la falla.
Como también puede leer en un comentario del manual de PHP , no hay un método para leer la propiedad del faultcode
, por lo que debe acceder directamente (por ejemplo, $e->faultcode
), porque el método getCode()
no funciona.
La especificación SOAP 1.1 define cuatro posibles valores para el campo de faultcode
:
- VersionMismatch : la parte procesadora encontró un espacio de nombres no válido para el elemento SOAP Envelope
- MustUnderstand : Un elemento hijo inmediato del elemento SOAP Header 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 error 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 errores del servidor 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 código HTTP
para identificar los errores que suceden a nivel de protocolo (p. Ej .: errores de socket); por ejemplo, si busca add_soap_fault
en el código fuente ext/soap/php_http.c , puede ver cuándo se generan algunos de estos tipos de fallas.
Al buscar las funciones add_soap_fault
y soap_server_fault
en los archivos fuente de extensión SOAP de PHP, he creado la siguiente lista de excepciones 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) 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 llamada de SoapServer.php
en el script SoapServer.php
con un tiempo (20) más largo que el tiempo (10) especificado para el parámetro default_socket_timeout
en el script SoapClient.php
.
Si desea simular una indisponibilidad del servicio, podría, por ejemplo, cambiar el protocolo de location
de http
a https
en el script soapclient.php
, suponiendo que su servidor web no está configurado para SSL; Al hacer esto, PHP debería arrojar un SoapFault "Could not connect to host".
Desde 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 está tratando de alcanzar está inactivo.
Luego aparece "Parece que no tenemos ningún documento XML", que es más críptico y puede significar cosas diferentes.
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);
}
Parece que 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 señala un comentario en la publicación del blog Robert Ludwick en una respuesta eliminada Timing Out PHP Soap Calls (21 de octubre de 2009, publicado por Robert F. Ludwick) , la solución discute la publicación (anulando SoapClient::__doRequest()
con un curl request) 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.
Simplemente establecer default_socket_timeout
globalmente a través de ini puede no hacer lo que desee. 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;
}
}
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).
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);
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);
}
}