it-swarm.com.de

Umgang mit Soap-Timeouts in PHP

Ich arbeite an einem Projekt, in dem ich Informationen eines Benutzers mit einem SOAP - Webdienst verifiziere. Ich kümmere mich zurzeit um Fehler, vorausgesetzt, ich erhalte Antworten vom Webdienst, muss aber auch die Edge-Fälle eines Service-Timeouts oder der Nichtverfügbarkeit behandeln.

Im Falle eines Timeouts oder einer Nichtverfügbarkeit eines Dienstes muss ich vorgeben, dass die Anforderung erfolgreich war (dass der Webservice die Informationen genehmigt hat), aber ich weiß nicht, welche Ausnahmen ausgelöst werden.

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

Was ich nicht finden kann, ist:

  1. Sind Timeouts eine SoapFault? Wenn ja, wie kann man am besten zwischen einem Timeout-Fehler und Web-Service-Problemen (z. B. einem Typfehler usw.) unterscheiden? Ich habe eine Seite gefunden, die einen Fehler erwähnte, bei dem die Nachricht etwas von "Fehler beim Laden von Headern" hatte, aber nicht erwähnt, ob dies ein Seifenfehler war.
  2. Wie kann es zu einer potenziellen Nichtverfügbarkeit eines Dienstes kommen? Eine PHP -Ausnahme scheint sinnvoll zu sein (ein SoapFault würde vom Webservice zurückgegeben, bei dem die Nichtverfügbarkeit ein Socket-Problem oder ähnliches sein würde).
  3. Gibt es einen vorhandenen Dienst (beispielsweise ein Beispiel), gegen den ich ein Timeout testen kann? Die meisten Zeitüberschreitungsdiskussionen scheinen mit der Verhinderung von Zeitüberschreitungen in Zusammenhang zu stehen, indem die Standardeinstellung für die Zeitüberschreitung verlängert wird.
32
Rob

1) Im Falle eines Timeouts löst PHP eine SoapFault-Ausnahme mit faultcode="HTTP" und faultstring="Error Fetching http headers" aus.

2) Meiner Meinung nach ist der beste Weg, um einen Timeout-Fehler von Web-Service-Problemen zu unterscheiden, die faultcode- und faultstring-Member der SoapFault-Klasse .
Das faultcode-Element ist insbesondere für die Verwendung durch Software vorgesehen, um einen algorithmischen Mechanismus zum Erkennen des Fehlers bereitzustellen.
Wie Sie auch in einem -Kommentar des PHP -Handbuchs nachlesen können, gibt es keine Methode zum Lesen der faultcode-Eigenschaft. Sie müssen also direkt darauf zugreifen (zB $e->faultcode), da getCode() Methode funktioniert nicht.
.__ Die SOAP 1.1 Spec definiert vier mögliche Werte für das Feld faultcode:

  • VersionMismatch: Die verarbeitende Partei hat einen ungültigen Namespace für das Envelope-Element SOAP gefunden
  • MustUnderstand: Ein unmittelbar untergeordnetes Element des SOAP - Headerelements, das von der verarbeitenden Partei entweder nicht verstanden wurde oder nicht befolgt wurde, enthielt ein SOAP - mustUnderstand-Attribut mit dem Wert "1".
  • Client: Die Client-Fehlerklasse zeigt an, dass die Nachricht falsch formatiert wurde oder nicht die entsprechenden Informationen enthielt, um erfolgreich zu sein. Beispielsweise könnte der Nachricht die richtige Authentifizierungs- oder Zahlungsinformation fehlen. Es ist im Allgemeinen ein Hinweis darauf, dass die Nachricht nicht ohne Änderung erneut gesendet werden sollte. 
  • Server: Die Fehlerklasse Server weist darauf hin, dass die Nachricht aus Gründen nicht verarbeitet werden konnte, die nicht direkt auf den Inhalt der Nachricht selbst, sondern auf die Verarbeitung der Nachricht zurückzuführen sind. Beispielsweise könnte die Verarbeitung die Kommunikation mit einem Upstream-Prozessor umfassen, der nicht reagiert hat. Die Nachricht kann zu einem späteren Zeitpunkt erfolgreich sein.

Zusätzlich zu diesen Codes verwendet PHP den Code HTTP, um die Fehler zu identifizieren, die auf Protokollebene auftreten (z. B .: Socket-Fehler). Wenn Sie beispielsweise im Quellcode ext/soap/php_http.c nach add_soap_fault suchen, können Sie sehen, wenn einige dieser Fehler generiert werden.
Durch die Suche nach den Funktionen add_soap_fault und soap_server_fault in den Erweiterungsquelldateien der Erweiterung PHP SOAP habe ich die folgende Liste von PHP SoapFault-Ausnahmen erstellt:

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) Versuchen Sie den folgenden Code, um die Timeout-Bedingung zu simulieren:

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

?>

Beachten Sie den Aufruf sleep im Skript SoapServer.php, dessen Zeit (20) länger ist als die Zeit (10), die für den Parameter default_socket_timeout im Skript SoapClient.php angegeben ist.
Wenn Sie die Nichtverfügbarkeit eines Dienstes simulieren möchten, können Sie beispielsweise das location-Protokoll von http in https im soapclient.php-Skript ändern, sofern Ihr Webserver nicht für SSL konfiguriert ist. Auf diese Weise sollte PHP einen SoapFault "Konnte keine Verbindung zum Host" herstellen.

32
user1419445

Sieht aus wie default_socket_timeout wird nicht berücksichtigt, wenn SOAP Anrufe über HTTPS getätigt werden:

Fehler zum Zeitpunkt des Schreibens offen. Als Kommentar zum Blogeintrag verwies Robert Ludwick in einer gelöschten Antwort auf Timing Out PHP Soap Calls (21. Oktober 2009; von Veröffentlicht von Robert F. Ludwick ) weist darauf hin, dass die in diesem Beitrag beschriebene Problemumgehung (Überschreiben von SoapClient::__doRequest() mit einer Curl-Anforderung) auch diesen Fehler umgeht.

Ein anderer verwandter Fehler ist:


Der im Blog-Beitrag erwähnte Code wurde geändert und kann in seiner neuesten Form mit Unterstützung der HTTP-Authentifizierung hier auf Github gefunden werden:

In jedem Fall sollte die Problemumgehung nicht länger benötigt werden, da dieses Problem in der Erweiterung PHP SOAPClient behoben wurde.

7
tobych

Mit Timeouts im Service umgehen

$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

Wenn nach meiner Erfahrung $e->getMessage "Fehler beim Abrufen von http-Headern" ist, handelt es sich um ein Netzwerk-Timeout.

Wenn $e->getMessage so etwas wie "Keine Verbindung zum Host herstellen" ist, ist der Dienst, den Sie erreichen möchten, inaktiv.

Dann gibt es "Sieht aus, als hätten wir kein XML-Dokument", was kryptischer ist und verschiedene Dinge bedeuten kann.

2
wosis

Ich habe zwei Faktoren verwendet, damit meine SoapClient-Erweiterung eine Nice-Ausnahme auslöst. Die Nachricht und die Zeit, zu der die Anforderung zurückkehrte. Ich denke die Fehlermeldung "Fehler beim Abrufen von http-Headern" kann auch in einigen anderen Fällen auftreten, daher die Zeitprüfung.

Der folgende Code sollte ungefähr richtig sein

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

Ich benutze dann 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();
}

Um Ihr Service-Zeitlimit zu debuggen. Fügen Sie die folgende Zeile hinzu, bevor Sie den Dienst aufrufen

ini_set('default_socket_timeout', 1);
1
HNygard

verwenden Sie einfach "stream_context", um die Timeout-Einstellung auch für das Laden von WSDL festzulegen (zuvor müssen Sie die SoapClient $ -Optionen ['connection_timeout'] einstellen):

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

Ich denke, ich bin etwas spät dran, aber falls jemand noch nach einer Lösung für Timeouts in php soap client sucht - hier ist was für mich funktioniert: 

Grundlegendes Ersetzen von PHP SoapClient durch cURL mit festgelegtem Timeout. Denken Sie daran, manchmal erwartet WS eine im HTTP-Header angegebene Aktion. Die ursprüngliche Lösung, die auf dieser Website veröffentlicht wurde, enthält dies nicht (Kommentare überprüfen).

0

Wenn Sie den default_socket_timeout global über das ini einstellen, tun Sie möglicherweise nicht das, was Sie möchten. Dies würde sich auf SOAP -Anforderungen auswirken, aber auch auf andere ausgehende Verbindungen, einschließlich DB-Verbindungen. Überschreiben Sie stattdessen die __doRequest () - Methode von SoapClient, um die HTTP-Verbindung selbst herzustellen. Sie können dann Ihr eigenes Timeout für das Socket festlegen, es erkennen und Ausnahmen auslösen, die Sie abfangen und handhaben können.

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