add demo file showcasing use of parallel calls; refactor Client class to support...
authorgggeek <giunta.gaetano@gmail.com>
Fri, 27 May 2022 10:18:17 +0000 (10:18 +0000)
committergggeek <giunta.gaetano@gmail.com>
Fri, 27 May 2022 10:18:17 +0000 (10:18 +0000)
NEWS
demo/client/_append.php
demo/client/_prepend.php
demo/client/parallel.php [new file with mode: 0644]
src/Client.php
src/PhpXmlRpc.php

diff --git a/NEWS b/NEWS
index aabea24..a044235 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,11 @@
+XML-RPC for PHP version 4.X.Y - unreleased
+
+* fixed: the `benchmark.php` file had seen some tests accidentally dropped
+
+* improved: added method `Client::prepareCurlHandle`, to make it easier to send multiple requests in parallel when using
+  curl and the server does not support `system.multicall`. See new demo file `parallel.php` for how this can be done.
+
+
 XML-RPC for PHP version 4.7.2 - 2022/5/25
 
 * modified the strings used to tell the client to use http/2: to avoid users mistaking 'http2' for the preferred value,
index da249b3..e235edf 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 
-// Out-of-band information: let the client manipulate the server operations.
+// Out-of-band information: let the client manipulate the page operations.
 // We do this to help the testsuite script: do not reproduce in production!
 if (isset($_COOKIE['PHPUNIT_SELENIUM_TEST_ID']) && extension_loaded('xdebug')) {
     include_once __DIR__ . "/../../vendor/phpunit/phpunit-selenium/PHPUnit/Extensions/SeleniumCommon/append.php";
index 710c477..8af98dc 100644 (file)
@@ -35,3 +35,9 @@ if (isset($_COOKIE['PHPUNIT_SELENIUM_TEST_ID']) && extension_loaded('xdebug')) {
 
     include_once __DIR__ . "/../../vendor/phpunit/phpunit-selenium/PHPUnit/Extensions/SeleniumCommon/prepend.php";
 }
+
+// A helper for cli vs we output:
+function output($text)
+{
+    echo PHP_SAPI == 'cli' ? strip_tags(str_replace('<br/>', "\n", $text)) : $text;
+}
diff --git a/demo/client/parallel.php b/demo/client/parallel.php
new file mode 100644 (file)
index 0000000..ace50e7
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+
+require_once __DIR__ . "/_prepend.php";
+
+use PhpXmlRpc\Encoder;
+use PhpXmlRpc\Client;
+use PhpXmlRpc\PhpXmlRpc;
+use PhpXmlRpc\Request;
+use PhpXmlRpc\Response;
+
+/**
+ * A class taking advantage of cURL to send many requests in parallel (to a single server), for when the given server
+ * does not support system.multicall method
+ */
+class ParallelClient extends Client
+{
+    public function sendParallel($requests, $timeout = 0, $method = '')
+    {
+        if ($method == '') {
+            $method = $this->method;
+        }
+
+        /// @todo validate that $method can be handled by the current curl install
+
+        $handles = array();
+        $curl = curl_multi_init();
+
+        foreach($requests as $k => $req) {
+            $req->setDebug($this->debug);
+
+            $handle = $this->prepareCurlHandle(
+                $req,
+                $this->server,
+                $this->port,
+                $timeout,
+                $this->username,
+                $this->password,
+                $this->authtype,
+                $this->cert,
+                $this->certpass,
+                $this->cacert,
+                $this->cacertdir,
+                $this->proxy,
+                $this->proxyport,
+                $this->proxy_user,
+                $this->proxy_pass,
+                $this->proxy_authtype,
+                $method,
+                false,
+                $this->key,
+                $this->keypass,
+                $this->sslversion
+            );
+            curl_multi_add_handle($curl, $handle);
+            $handles[$k] = $handle;
+        }
+
+        $running = 0;
+        do {
+            curl_multi_exec($curl, $running);
+        } while($running > 0);
+
+        $responses = array();
+        foreach($handles as $k => $h) {
+            $responses[$k] = curl_multi_getcontent($handles[$k]);
+
+            if ($this->debug > 1) {
+                $message = "---CURL INFO---\n";
+                foreach (curl_getinfo($h) as $name => $val) {
+                    if (is_array($val)) {
+                        $val = implode("\n", $val);
+                    }
+                    $message .= $name . ': ' . $val . "\n";
+                }
+                $message .= '---END---';
+                $this->getLogger()->debugMessage($message);
+            }
+
+            //curl_close($h);
+            curl_multi_remove_handle($curl, $h);
+        }
+        curl_multi_close($curl);
+
+        foreach($responses as $k => $resp) {
+            if (!$resp) {
+                $responses[$k] = new Response(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . ': ' . curl_error($curl));
+            } else {
+                $responses[$k] = $requests[$k]->parseResponse($resp, true, $this->return_type);
+            }
+        }
+
+        return $responses;
+    }
+}
+
+$num_tests = 25;
+
+$data = array(1, 1.0, 'hello world', true, '20051021T23:43:00', -1, 11.0, '~!@#$%^&*()_+|', false, '20051021T23:43:00');
+$encoder = new Encoder();
+$value = $encoder->encode($data, array('auto_dates'));
+$req = new Request('interopEchoTests.echoValue', array($value));
+$reqs = array();
+for ($i = 0; $i < $num_tests; $i++) {
+    $reqs[] = $req;
+}
+
+$client = new ParallelClient(XMLRPCSERVER);
+$client->no_multicall = true;
+
+$t = microtime(true);
+$resp = $client->send($reqs);
+$t = microtime(true) - $t;
+echo "Sequential send: $t secs\n";
+
+$t = microtime(true);
+$resp = $client->sendParallel($reqs);
+$t = microtime(true) - $t;
+echo "Parallel send: $t secs\n";
+
+$client->no_multicall = false;
+$t = microtime(true);
+$resp = $client->send($reqs);
+$t = microtime(true) - $t;
+echo "Multicall send: $t secs\n";
+
+require_once __DIR__ . "/_append.php";
index c9c6297..0fccec7 100644 (file)
@@ -490,6 +490,7 @@ class Client
      *
      * @return Response|Response[] Note that the client will always return a Response object, even if the call fails
      * @todo allow throwing exceptions instead of returning responses in case of failed calls and/or Fault responses
+     * @todo refactor: we now support many options besides connection timeout and http version to use. Why only privilege those?
      */
     public function send($req, $timeout = 0, $method = '')
     {
@@ -657,12 +658,16 @@ class Client
      * @param string $keyPass @todo not implemented yet.
      * @param int $sslVersion @todo not implemented yet. See http://php.net/manual/en/migration56.openssl.php
      * @return Response
+     *
+     * @todo refactor: we get many options for the call passed in, but some we use from $this. We should clean that up
      */
     protected function sendPayloadSocket($req, $server, $port, $timeout = 0, $username = '', $password = '',
         $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
         $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method='http', $key = '', $keyPass = '',
         $sslVersion = 0)
     {
+        /// @todo log a warning if passed an unsupported method
+
         if ($port == 0) {
             $port = ( $method === 'https' ) ? 443 : 80;
         }
@@ -875,6 +880,8 @@ class Client
      * @param string $keyPass
      * @param int $sslVersion
      * @return Response
+     *
+     * @todo refactor: we get many options for the call passed in, but some we use from $this. We should clean that up
      */
     protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username = '', $password = '',
         $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
@@ -900,6 +907,54 @@ class Client
             return new Response(0, PhpXmlRpc::$xmlrpcerr['no_http2'], PhpXmlRpc::$xmlrpcstr['no_http2']);
         }
 
+        $curl = $this->prepareCurlHandle($req, $server, $port, $timeout, $username, $password,
+            $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
+            $proxyUsername, $proxyPassword, $proxyAuthType, $method, $keepAlive, $key,
+            $keyPass, $sslVersion);
+
+        $result = curl_exec($curl);
+
+        if ($this->debug > 1) {
+            $message = "---CURL INFO---\n";
+            foreach (curl_getinfo($curl) as $name => $val) {
+                if (is_array($val)) {
+                    $val = implode("\n", $val);
+                }
+                $message .= $name . ': ' . $val . "\n";
+            }
+            $message .= '---END---';
+            $this->getLogger()->debugMessage($message);
+        }
+
+        if (!$result) {
+            /// @todo we should use a better check here - what if we get back '' or '0'?
+
+            $this->errstr = 'no response';
+            $resp = new Response(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . ': ' . curl_error($curl));
+            curl_close($curl);
+            if ($keepAlive) {
+                $this->xmlrpc_curl_handle = null;
+            }
+        } else {
+            if (!$keepAlive) {
+                curl_close($curl);
+            }
+            $resp = $req->parseResponse($result, true, $this->return_type);
+            // if we got back a 302, we can not reuse the curl handle for later calls
+            if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error'] && $keepAlive) {
+                curl_close($curl);
+                $this->xmlrpc_curl_handle = null;
+            }
+        }
+
+        return $resp;
+    }
+
+    protected function prepareCurlHandle($req, $server, $port, $timeout = 0, $username = '', $password = '',
+         $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
+         $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
+         $keyPass = '', $sslVersion = 0)
+    {
         if ($port == 0) {
             if (in_array($method, array('http', 'http10', 'http11', 'h2c'))) {
                 $port = 80;
@@ -933,10 +988,6 @@ class Client
             $encodingHdr = '';
         }
 
-        if ($this->debug > 1) {
-            $this->getLogger()->debugMessage("---SENDING---\n$payload\n---END---");
-        }
-
         if (!$keepAlive || !$this->xmlrpc_curl_handle) {
             if ($method == 'http11' || $method == 'http10' || $method == 'h2c') {
                 $protocol = 'http';
@@ -944,6 +995,7 @@ class Client
                 if ($method == 'h2') {
                     $protocol = 'https';
                 } else {
+                    // http, https
                     $protocol = $method;
                 }
             }
@@ -1090,42 +1142,11 @@ class Client
             curl_setopt($curl, $opt, $val);
         }
 
-        $result = curl_exec($curl);
-
         if ($this->debug > 1) {
-            $message = "---CURL INFO---\n";
-            foreach (curl_getinfo($curl) as $name => $val) {
-                if (is_array($val)) {
-                    $val = implode("\n", $val);
-                }
-                $message .= $name . ': ' . $val . "\n";
-            }
-            $message .= '---END---';
-            $this->getLogger()->debugMessage($message);
-        }
-
-        if (!$result) {
-            /// @todo we should use a better check here - what if we get back '' or '0'?
-
-            $this->errstr = 'no response';
-            $resp = new Response(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . ': ' . curl_error($curl));
-            curl_close($curl);
-            if ($keepAlive) {
-                $this->xmlrpc_curl_handle = null;
-            }
-        } else {
-            if (!$keepAlive) {
-                curl_close($curl);
-            }
-            $resp = $req->parseResponse($result, true, $this->return_type);
-            // if we got back a 302, we can not reuse the curl handle for later calls
-            if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error'] && $keepAlive) {
-                curl_close($curl);
-                $this->xmlrpc_curl_handle = null;
-            }
+            $this->getLogger()->debugMessage("---SENDING---\n$payload\n---END---");
         }
 
-        return $resp;
+        return $curl;
     }
 
     /**
@@ -1209,7 +1230,7 @@ class Client
      * @param Request[] $reqs
      * @param int $timeout
      * @param string $method
-     * @return Response[]|bool|mixed|Response
+     * @return Response[]|false|mixed|Response
      */
     private function _try_multicall($reqs, $timeout, $method)
     {
index 901d1eb..cc79351 100644 (file)
@@ -80,7 +80,7 @@ class PhpXmlRpc
     public static $xmlrpc_internalencoding = "UTF-8";
 
     public static $xmlrpcName = "XML-RPC for PHP";
-    public static $xmlrpcVersion = "4.7.1";
+    public static $xmlrpcVersion = "4.8.0-dev";
 
     // let user errors start at 800
     public static $xmlrpcerruser = 800;