web-dev-qa-db-ja.com

PHPでのSOAPタイムアウトの処理

SOAP Webサービスを使用してユーザーからの情報を確認するプロジェクトに取り組んでいます。現在、Webサービスから応答を受け取っていると仮定してエラーを処理していますが、また、サービスタイムアウトまたは使用不能のEdgeケースを処理する必要があります。

タイムアウトまたはサービスが利用できない場合、リクエストが成功した(Webサービスが情報を承認した)ふりをする必要がありますが、どの例外がスローされるのか明確ではありません。

いくつかの擬似コード:

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

私が見つけられないように見えるのは:

  1. タイムアウトはSoapFaultですか?もしそうなら、タイムアウトエラーとWebサービスの問題(タイプエラーなど)を区別する最良の方法は何ですか?メッセージが「ヘッダーの読み込みエラー」の結果であるというエラーについて言及しているページを見つけましたが、これがSOAPエラーかどうかについては言及していません。
  2. サービスが利用できなくなる可能性はどのように起こりますか? PHP例外は理にかなっているようです(SoapFaultはWebサービスから返され、そこではソケットの問題などが利用可能になります)?
  3. タイムアウトをテストできる既存のサービス(例など)はありますか?ほとんどのタイムアウト関連の議論は、デフォルトのタイムアウト設定を拡張することでタイムアウトを防止することに関連しているように見えますが、これはこの状況では理想的ではありません。
32
Rob

_default_socket_timeout_ のように見えますSOAP HTTPS経由の呼び出しを行う場合:

執筆時点で開いているバグ。削除された回答で参照されたブログ投稿Robert Ludwickへのコメントとして Timing Out PHP Soap Calls(2009年10月21日、発行者Robert F. Ludwick) 指摘、投稿の回避策( SoapClient::__doRequest() をcurlリクエストでオーバーライド)がこのバグを回避する。

別の関連するバグは次のとおりです。


ブログの投稿で言及されているコードはいくつかの変更が加えられており、GithubのHTTP認証をサポートする最新の形式で見つけることができます。

いずれの場合も、この問題はPHP SOAPClient拡張機能で修正されているため、回避策はもう必要ありません。

7
tobych

サービスのタイムアウトに対処するには

$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

私の経験から、$e->getMessageは「HTTPヘッダーのフェッチエラー」であり、ネットワークタイムアウトを処理しています。

$e->getMessageは「ホストに接続できません」のようなもので、到達しようとしているサービスがダウンしています。

次に、「XMLドキュメントを取得していないように見えます」があります。

2
wosis

SoapClient拡張を取得してNice例外をスローするために、2つの要因を使用しました。メッセージとリクエストが返されるまでにかかった時間。エラーメッセージ「Error Fetching http headers」は他のいくつかのケースでも発生する可能性があるため、時間をチェックします。

次のコードはほぼ正しいはずです

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

次に、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();
}

サービスのタイムアウトをデバッグするには。サービスを呼び出す前に次の行を追加します

ini_set('default_socket_timeout', 1);
1
HNygard

「stream_context」を使用して、WSDLロードのタイムアウト設定も設定します(前にSoapClient $ options ['connection_timeout']を設定する必要があります)。

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

私は少し遅れていると思いますが、誰かがまだPHP SOAPクライアントでタイムアウトの解決策を探している場合-ここに私のために働いているものがあります:

基本的にPHPタイムアウトを設定したSoapClientをcURLに置き換えます。覚えておいてください。WSはHTTPヘッダーで指定されたアクションを予期する場合があります。

0

default_socket_timeout iniを介してグローバルにあなたが望むことをしないかもしれません。これはSOAPリクエストに影響しますが、DB接続を含む他の発信接続にも影響します。代わりに、SoapClientの__doRequest()メソッドをオーバーライドしてHTTP接続を自分で作成します。ソケット、それを検出し、トラップして処理できる例外をスローします。

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