Je travaille sur un projet dans lequel je vérifie les informations d'un utilisateur avec un service Web SOAP. Je m'occupe actuellement des erreurs en supposant que je reçois des réponses du service Web, mais que je dois également gérer les cas Edge d'un délai de service ou d'une indisponibilité.
En cas d'expiration du délai ou d'indisponibilité du service, je dois prétendre que la demande a abouti (que le service Web a approuvé les informations), mais je ne sais pas exactement quelles exceptions sont levées.
Quelques pseudo-codes:
// $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
}
Ce que je n'arrive pas à trouver, c'est:
SoapFault
? Si tel est le cas, quel est le meilleur moyen de faire la distinction entre une erreur de dépassement de délai et des problèmes de service Web (tels qu'une erreur de type, etc.)? J'ai trouvé une page qui mentionnait une erreur dans laquelle le message ressemblait à "Erreur lors du chargement des en-têtes", sans préciser s'il s'agissait d'une erreur SOAP.1) En cas de dépassement du délai d'attente, PHP lève une exception SoapFault avec faultcode="HTTP"
et faultstring="Error Fetching http headers"
.
2) À mon avis, le meilleur moyen de distinguer une erreur de dépassement de délai d'attente d'un problème de service Web consiste à examiner les membres faultcode
et faultstring
de la classe SoapFault .
En particulier, l’élément faultcode
est destiné à être utilisé par un logiciel pour fournir un mécanisme algorithmique permettant d’identifier le défaut.
Comme vous pouvez également lire un commentaire du manuel PHP , il n’existe pas de méthode permettant de lire la propriété faultcode
; vous devez donc y accéder directement (par exemple, $e->faultcode
), car la fonction getCode()
. méthode ne fonctionne pas.
La spécification SOAP 1.1 définit quatre valeurs possibles pour le champ faultcode
:
En plus de ces codes, PHP utilise le code HTTP
pour identifier les erreurs survenant au niveau du protocole (par exemple: erreurs de socket); Par exemple, si vous recherchez add_soap_fault
dans le code source ext/soap/php_http.c , vous pouvez voir quand certains de ces types de défauts sont générés.
En recherchant les fonctions add_soap_fault
et soap_server_fault
dans les fichiers sources de l'extension PHP SOAP, j'ai construit la liste suivante des exceptions 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) Pour simuler la condition de délai d'attente, essayez avec le code suivant:
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();
?>
Notez l'appel sleep
dans le script SoapServer.php
avec un temps (20) plus long que le temps (10) spécifié pour le paramètre default_socket_timeout
dans le script SoapClient.php
.
Si vous souhaitez simuler une indisponibilité de service, vous pouvez par exemple modifier le protocole location
de http
à https
dans le script soapclient.php
, en supposant que votre serveur Web ne soit pas configuré pour SSL; en faisant cela, PHP devrait lancer un SoapFault "Impossible de se connecter à l'hôte".
On dirait que default_socket_timeout
n'est pas pris en compte lors d'appels SOAP via HTTPS:
Bug ouvert au moment de l'écriture. Comme un commentaire sur le billet de blog de Robert Ludwick, référencé dans une réponse supprimée Timing Out PHP Appels de savon (21 oct 2009; par Publié par Robert F. Ludwick) signale la solution Le message discussion (remplacer SoapClient::__doRequest()
avec une requête curl) fonctionne également autour de ce bogue.
Un autre bug lié est:
Le code mentionné dans l'article de blog a subi quelques modifications et peut être trouvé dans sa dernière forme avec le support de l'authentification HTTP ici sur Github:
Dans tous les cas, la solution de contournement ne devrait plus être nécessaire car ce problème a été résolu dans l'extension SOAPClient PHP.
Pour gérer les délais d'attente dans le service
$client = new SoapClient($wsdl, array("connection_timeout"=>10));
// SET SOCKET TIMEOUT
if(defined('RESPONSE_TIMEOUT') && RESPONSE_TIMEOUT != '') {
ini_set('default_socket_timeout', RESPONSE_TIMEOUT);
}
D'après mon expérience, si $e->getMessage
est "Erreur lors de la récupération des en-têtes http", vous avez un délai d'attente réseau.
Si $e->getMessage
est quelque chose comme "Impossible de se connecter à l'hôte", le service que vous essayez d'atteindre est hors service.
Ensuite, il y a "On dirait que nous n'avons aucun document XML", ce qui est plus cryptique et peut signifier différentes choses.
J'ai utilisé deux facteurs pour obtenir mon extension SoapClient et lancer une exception Nice. Le message et le temps que la demande a pris pour revenir. Je pense que le message d'erreur "Erreur lors de la récupération des en-têtes http" peut également se produire dans d'autres cas, d'où la vérification de l'heure.
Le code suivant devrait être à peu près correct
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 {}
J'utilise ensuite 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();
}
Pour déboguer le délai d'attente de votre service. Ajoutez la ligne suivante avant d'appeler le service
ini_set('default_socket_timeout', 1);
utilisez simplement "stream_context" pour définir le paramètre de délai d'attente également pour le chargement WSDL (vous devez définir les options $ SoapClient ['connection_timeout'] auparavant):
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);
}
}
Je suppose que je suis un peu en retard, mais au cas où quelqu'un chercherait toujours une solution au délai d'attente d'un client de savon php, voici ce qui a fonctionné pour moi:
Remplacer simplement PHP SoapClient par cURL avec le délai d'attente défini. N'oubliez pas que parfois WS attend l'action spécifiée dans l'en-tête HTTP. La solution originale publiée sur ce site Web n'inclut pas cela (vérifier les commentaires).
Définir simplement le default_socket_timeout
globalement via l’ini peut ne pas faire ce que vous voulez. Cela affecterait les requêtes SOAP, mais affecterait également les autres connexions sortantes, y compris les connexions à la base de données. Remplacez plutôt la méthode __doRequest () de SoapClient pour établir vous-même la connexion HTTP. Vous pouvez ensuite définir votre propre délai d'attente sur le socket, le détecter et émettre des exceptions que vous pouvez intercepter et gérer.
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;
}
}