--- /dev/null
+<?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";
*
* @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 = '')
{
* @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;
}
* @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,
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;
$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';
if ($method == 'h2') {
$protocol = 'https';
} else {
+ // http, https
$protocol = $method;
}
}
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;
}
/**
* @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)
{