From 4fa56d55fcde316352b70ff62502994558cf3de3 Mon Sep 17 00:00:00 2001 From: gggeek Date: Fri, 27 May 2022 10:18:17 +0000 Subject: [PATCH] add demo file showcasing use of parallel calls; refactor Client class to support that --- NEWS | 8 +++ demo/client/_append.php | 2 +- demo/client/_prepend.php | 6 ++ demo/client/parallel.php | 126 +++++++++++++++++++++++++++++++++++++++ src/Client.php | 97 ++++++++++++++++++------------ src/PhpXmlRpc.php | 2 +- 6 files changed, 201 insertions(+), 40 deletions(-) create mode 100644 demo/client/parallel.php diff --git a/NEWS b/NEWS index aabea24e..a0442350 100644 --- 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, diff --git a/demo/client/_append.php b/demo/client/_append.php index da249b30..e235edfc 100644 --- a/demo/client/_append.php +++ b/demo/client/_append.php @@ -1,6 +1,6 @@ ', "\n", $text)) : $text; +} diff --git a/demo/client/parallel.php b/demo/client/parallel.php new file mode 100644 index 00000000..ace50e72 --- /dev/null +++ b/demo/client/parallel.php @@ -0,0 +1,126 @@ +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"; diff --git a/src/Client.php b/src/Client.php index c9c62975..0fccec7f 100644 --- a/src/Client.php +++ b/src/Client.php @@ -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) { diff --git a/src/PhpXmlRpc.php b/src/PhpXmlRpc.php index 901d1eb1..cc793512 100644 --- a/src/PhpXmlRpc.php +++ b/src/PhpXmlRpc.php @@ -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; -- 2.47.0