web-dev-qa-db-fra.com

Traitement des délais d'attente de savon dans PHP

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:

  1. Les délais d'attente sont-ils 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.
  2. Comment une indisponibilité de service va-t-elle potentiellement se produire? Une exception PHP semble logique (un SoapFault serait renvoyé du service Web où l'indisponibilité serait un problème de socket ou similaire)?
  3. Existe-t-il un service existant (par exemple, un exemple) avec lequel je peux tester un délai d'attente? La plupart des discussions relatives au délai d'attente semblent liées à la prévention du délai d'attente en prolongeant le paramètre de délai d'attente par défaut, ce qui n'est pas idéal dans cette situation.
32
Rob

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:

  • VersionMismatch: la partie responsable du traitement a trouvé un espace de nom non valide pour l'élément Envelope SOAP
  • MustUnderstand: un élément enfant immédiat de l'élément d'en-tête SOAP qui n'a pas été compris ou n'a pas été respecté par la partie de traitement contenant un attribut SOAP mustUnderstand avec la valeur "1"
  • Client: la classe d'erreurs Client indique que le message a été mal formé ou ne contient pas les informations appropriées pour réussir. Par exemple, le message peut ne pas disposer des informations d'authentification ou de paiement appropriées. Cela indique généralement que le message ne doit pas être renvoyé sans changement. 
  • Server: la classe d'erreurs Server indique que le message n'a pas pu être traité pour des raisons non directement imputables au contenu du message, mais au traitement du message. Par exemple, le traitement pourrait inclure la communication avec un processeur en amont, qui n'a pas répondu. Le message peut réussir ultérieurement.

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".

32
user1419445

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.

7
tobych

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);
}
3
ToughPal

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.

2
wosis

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);
1
HNygard

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);
  }
}
0
sh3b4ng

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).

0
Maciej Jaśniaczyk

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

}
0
Derek