From cc652e4bdf2da6aa2f740e3ccd706bfb9db0560f Mon Sep 17 00:00:00 2001 From: Ondrej Novak Date: Sun, 23 Oct 2016 21:32:24 +0200 Subject: [PATCH] configurable timeout for response (default 2500ms) --- src/Dispatcher.php | 521 +++++++++++++++++++++++---------------------- src/SoapClient.php | 363 ++++++++++++++++++++++--------- src/Utils/UUID.php | 32 +++ 3 files changed, 553 insertions(+), 363 deletions(-) create mode 100644 src/Utils/UUID.php diff --git a/src/Dispatcher.php b/src/Dispatcher.php index 9b1ec67..eae4794 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -1,257 +1,264 @@ -service = $service; - $this->key = $key; - $this->cert = $cert; - $this->checkRequirements(); - } - - /** - * - * @param string $service - * @param Receipt $receipt - * @return boolean|string - */ - public function check(Receipt $receipt) { - try { - return $this->send($receipt, TRUE); - } catch (ServerException $e) { - return FALSE; - } - } - - /** - * - * @param boolean $tillLastRequest optional If not set/FALSE connection time till now is returned. - * @return float - */ - public function getConnectionTime($tillLastRequest = FALSE) { - !$this->trace && $this->throwTraceNotEnabled(); - return $this->getSoapClient()->__getConnectionTime($tillLastRequest); - } - - /** - * - * @return int - */ - public function getLastResponseSize() { - !$this->trace && $this->throwTraceNotEnabled(); - return mb_strlen($this->getSoapClient()->__getLastResponse(), '8bit'); - } - - /** - * - * @return int - */ - public function getLastRequestSize() { - !$this->trace && $this->throwTraceNotEnabled(); - return mb_strlen($this->getSoapClient()->__getLastRequest(), '8bit'); - } - - /** - * - * @return float time in ms - */ - public function getLastResponseTime() { - !$this->trace && $this->throwTraceNotEnabled(); - return $this->getSoapClient()->__getLastResponseTime(); - } - - /** - * - * @throws ClientException - */ - private function throwTraceNotEnabled() { - throw new ClientException('Trace is not enabled! Set trace property to TRUE.'); - } - - /** - * - * @param \Ondrejnov\EET\Receipt $receipt - * @return array - */ - public function getCheckCodes(Receipt $receipt) { - $objKey = new \XMLSecurityKey(\XMLSecurityKey::RSA_SHA256, ['type' => 'private']); - $objKey->loadKey($this->key, TRUE); - - $arr = [ - $receipt->dic_popl, - $receipt->id_provoz, - $receipt->id_pokl, - $receipt->porad_cis, - $receipt->dat_trzby->format('c'), - Format::price($receipt->celk_trzba) - ]; - $sign = $objKey->signData(join('|', $arr)); - - return [ - 'pkp' => [ - '_' => $sign, - 'digest' => 'SHA256', - 'cipher' => 'RSA2048', - 'encoding' => 'base64' - ], - 'bkp' => [ - '_' => Format::BKB(sha1($sign)), - 'digest' => 'SHA1', - 'encoding' => 'base16' - ] - ]; - } - - /** - * - * @param Receipt $receipt - * @param boolean $check - * @return boolean|string - */ - public function send(Receipt $receipt, $check = FALSE) { - $this->initSoapClient(); - - $response = $this->processData($receipt, $check); - - isset($response->Chyba) && $this->processError($response->Chyba); - - return $check ? TRUE : $response->Potvrzeni->fik; - } - - /** - * - * @throws RequirementsException - * @return void - */ - private function checkRequirements() { - if (!class_exists('\SoapClient')) { - throw new RequirementsException('Class SoapClient is not defined! Please, allow php extension php_soap.dll in php.ini'); - } - } - - /** - * Get (or if not exists: initialize and get) SOAP client. - * - * @return SoapClient - */ - private function getSoapClient() { - !isset($this->soapClient) && $this->initSoapClient(); - return $this->soapClient; - } - - /** - * Require to initialize a new SOAP client for a new request. - * - * @return void - */ - private function initSoapClient() { - $this->soapClient = new SoapClient($this->service, $this->key, $this->cert, $this->trace); - } - - /** - * - * @param Receipt $receipt - * @param boolean $check - * @return object - */ - private function processData(Receipt $receipt, $check = FALSE) { - $head = [ - 'uuid_zpravy' => $receipt->uuid_zpravy, - 'dat_odesl' => time(), - 'prvni_zaslani' => $receipt->prvni_zaslani, - 'overeni' => $check - ]; - - $body = [ - 'dic_popl' => $receipt->dic_popl, - 'dic_poverujiciho' => $receipt->dic_poverujiciho, - 'id_provoz' => $receipt->id_provoz, - 'id_pokl' => $receipt->id_pokl, - 'porad_cis' => $receipt->porad_cis, - 'dat_trzby' => $receipt->dat_trzby->format('c'), - 'celk_trzba' => Format::price($receipt->celk_trzba), - 'zakl_nepodl_dph' => Format::price($receipt->zakl_nepodl_dph), - 'zakl_dan1' => Format::price($receipt->zakl_dan1), - 'dan1' => Format::price($receipt->dan1), - 'zakl_dan2' => Format::price($receipt->zakl_dan2), - 'dan2' => Format::price($receipt->dan2), - 'zakl_dan3' => Format::price($receipt->zakl_dan3), - 'dan3' => Format::price($receipt->dan3), - 'rezim' => $receipt->rezim - ]; - - return $this->getSoapClient()->OdeslaniTrzby([ - 'Hlavicka' => $head, - 'Data' => $body, - 'KontrolniKody' => $this->getCheckCodes($receipt) - ] - ); - } - - /** - * @param $error - * @throws ServerException - */ - private function processError($error) { - if ($error->kod) { - $msgs = [ - -1 => 'Docasna technicka chyba zpracovani – odeslete prosim datovou zpravu pozdeji', - 2 => 'Kodovani XML neni platne', - 3 => 'XML zprava nevyhovela kontrole XML schematu', - 4 => 'Neplatny podpis SOAP zpravy', - 5 => 'Neplatny kontrolni bezpecnostni kod poplatnika (BKP)', - 6 => 'DIC poplatnika ma chybnou strukturu', - 7 => 'Datova zprava je prilis velka', - 8 => 'Datova zprava nebyla zpracovana kvuli technicke chybe nebo chybe dat', - ]; - $msg = isset($msgs[$error->kod]) ? $msgs[$error->kod] : ''; - throw new ServerException($msg, $error->kod); - } - } - -} +service = $service; + $this->key = $key; + $this->cert = $cert; + $this->checkRequirements(); + } + + /** + * + * @param string $service + * @param Receipt $receipt + * @return boolean|string + */ + public function check(Receipt $receipt) { + try { + return $this->send($receipt, TRUE); + } catch (ServerException $e) { + return FALSE; + } + } + + /** + * + * @param boolean $tillLastRequest optional If not set/FALSE connection time till now is returned. + * @return float + */ + public function getConnectionTime($tillLastRequest = FALSE) { + !$this->trace && $this->throwTraceNotEnabled(); + return $this->getSoapClient()->__getConnectionTime($tillLastRequest); + } + + /** + * + * @return int + */ + public function getLastResponseSize() { + !$this->trace && $this->throwTraceNotEnabled(); + return mb_strlen($this->getSoapClient()->__getLastResponse(), '8bit'); + } + + /** + * + * @return int + */ + public function getLastRequestSize() { + !$this->trace && $this->throwTraceNotEnabled(); + return mb_strlen($this->getSoapClient()->__getLastRequest(), '8bit'); + } + + /** + * + * @return float time in ms + */ + public function getLastResponseTime() { + !$this->trace && $this->throwTraceNotEnabled(); + return $this->getSoapClient()->__getLastResponseTime(); + } + + /** + * + * @throws ClientException + */ + private function throwTraceNotEnabled() { + throw new ClientException('Trace is not enabled! Set trace property to TRUE.'); + } + + /** + * + * @param \Ondrejnov\EET\Receipt $receipt + * @return array + */ + public function getCheckCodes(Receipt $receipt) { + $objKey = new \XMLSecurityKey(\XMLSecurityKey::RSA_SHA256, ['type' => 'private']); + $objKey->loadKey($this->key, TRUE); + + $arr = [ + $receipt->dic_popl, + $receipt->id_provoz, + $receipt->id_pokl, + $receipt->porad_cis, + $receipt->dat_trzby->format('c'), + Format::price($receipt->celk_trzba) + ]; + $sign = $objKey->signData(join('|', $arr)); + + return [ + 'pkp' => [ + '_' => $sign, + 'digest' => 'SHA256', + 'cipher' => 'RSA2048', + 'encoding' => 'base64' + ], + 'bkp' => [ + '_' => Format::BKB(sha1($sign)), + 'digest' => 'SHA1', + 'encoding' => 'base16' + ] + ]; + } + + /** + * + * @param Receipt $receipt + * @param boolean $check + * @return boolean|string + */ + public function send(Receipt $receipt, $check = FALSE) { + $this->initSoapClient(); + + $response = $this->processData($receipt, $check); + + isset($response->Chyba) && $this->processError($response->Chyba); + + return $check ? TRUE : $response->Potvrzeni->fik; + } + + /** + * + * @throws RequirementsException + * @return void + */ + private function checkRequirements() { + if (!class_exists('\SoapClient')) { + throw new RequirementsException('Class SoapClient is not defined! Please, allow php extension php_soap.dll in php.ini'); + } + } + + /** + * Get (or if not exists: initialize and get) SOAP client. + * + * @return SoapClient + */ + public function getSoapClient() { + !isset($this->soapClient) && $this->initSoapClient(); + return $this->soapClient; + } + + /** + * Require to initialize a new SOAP client for a new request. + * + * @return void + */ + private function initSoapClient() { + if ($this->soapClient === NULL) { + $this->soapClient = new SoapClient($this->service, $this->key, $this->cert, $this->trace); + } + } + + public function prepareData($receipt, $check = FALSE) { + $head = [ + 'uuid_zpravy' => $receipt->uuid_zpravy, + 'dat_odesl' => time(), + 'prvni_zaslani' => $receipt->prvni_zaslani, + 'overeni' => $check + ]; + + $body = [ + 'dic_popl' => $receipt->dic_popl, + 'dic_poverujiciho' => $receipt->dic_poverujiciho, + 'id_provoz' => $receipt->id_provoz, + 'id_pokl' => $receipt->id_pokl, + 'porad_cis' => $receipt->porad_cis, + 'dat_trzby' => $receipt->dat_trzby->format('c'), + 'celk_trzba' => Format::price($receipt->celk_trzba), + 'zakl_nepodl_dph' => Format::price($receipt->zakl_nepodl_dph), + 'zakl_dan1' => Format::price($receipt->zakl_dan1), + 'dan1' => Format::price($receipt->dan1), + 'zakl_dan2' => Format::price($receipt->zakl_dan2), + 'dan2' => Format::price($receipt->dan2), + 'zakl_dan3' => Format::price($receipt->zakl_dan3), + 'dan3' => Format::price($receipt->dan3), + 'rezim' => $receipt->rezim + ]; + + return [ + 'Hlavicka' => $head, + 'Data' => $body, + 'KontrolniKody' => $this->getCheckCodes($receipt) + ]; + } + + /** + * + * @param Receipt $receipt + * @param boolean $check + * @return object + */ + private function processData(Receipt $receipt, $check = FALSE) { + $data = $this->prepareData($receipt, $check); + + return $this->getSoapClient()->OdeslaniTrzby($data); + } + + /** + * @param $error + * @throws ServerException + */ + private function processError($error) { + if ($error->kod) { + $msgs = [ + -1 => 'Docasna technicka chyba zpracovani – odeslete prosim datovou zpravu pozdeji', + 2 => 'Kodovani XML neni platne', + 3 => 'XML zprava nevyhovela kontrole XML schematu', + 4 => 'Neplatny podpis SOAP zpravy', + 5 => 'Neplatny kontrolni bezpecnostni kod poplatnika (BKP)', + 6 => 'DIC poplatnika ma chybnou strukturu', + 7 => 'Datova zprava je prilis velka', + 8 => 'Datova zprava nebyla zpracovana kvuli technicke chybe nebo chybe dat', + ]; + $msg = isset($msgs[$error->kod]) ? $msgs[$error->kod] : ''; + throw new ServerException($msg, $error->kod); + } + } + +} diff --git a/src/SoapClient.php b/src/SoapClient.php index 491ada3..989ef60 100644 --- a/src/SoapClient.php +++ b/src/SoapClient.php @@ -1,106 +1,257 @@ -connectionStartTime = microtime(TRUE); - parent::__construct($service, [ - 'exceptions' => TRUE, - 'trace' => $trace - ]); - $this->key = $key; - $this->cert = $cert; - $this->traceRequired = $trace; - } - - public function __doRequest($request, $location, $saction, $version, $one_way = NULL) { - $doc = new \DOMDocument('1.0'); - $doc->loadXML($request); - - $objWSSE = new \WSSESoap($doc); - $objWSSE->addTimestamp(); - - $objKey = new \XMLSecurityKey(\XMLSecurityKey::RSA_SHA256, ['type' => 'private']); - $objKey->loadKey($this->key, TRUE); - $objWSSE->signSoapDoc($objKey, ["algorithm" => \XMLSecurityDSig::SHA256]); - - $token = $objWSSE->addBinaryToken(file_get_contents($this->cert)); - $objWSSE->attachTokentoSig($token); - - $this->traceRequired && $this->lastResponseStartTime = microtime(TRUE); - - $response = parent::__doRequest($this->lastRequest = $objWSSE->saveXML(), $location, $saction, $version); - - $this->traceRequired && $this->lastResponseEndTime = microtime(TRUE); - - return $response; - } - - /** - * - * @return float - */ - public function __getLastResponseTime() { - return $this->lastResponseEndTime - $this->lastResponseStartTime; - } - - /** - * - * @return float - */ - public function __getConnectionTime($tillLastRequest = FALSE) { - return $tillLastRequest ? $this->getConnectionTimeTillLastRequest() : $this->getConnectionTimeTillNow(); - } - - private function getConnectionTimeTillLastRequest() { - if (!$this->lastResponseEndTime || !$this->connectionStartTime) { - return NULL; - } - return $this->lastResponseEndTime - $this->connectionStartTime; - } - - private function getConnectionTimeTillNow() { - if (!$this->connectionStartTime) { - return NULL; - } - return microtime(TRUE) - $this->connectionStartTime; - } - - /** - * @return string - */ - public function __getLastRequest() { - return $this->lastRequest; - } - -} +connectionStartTime = microtime(TRUE); + parent::__construct($service, [ + 'exceptions' => TRUE, + 'trace' => $trace + ]); + $this->key = $key; + $this->cert = $cert; + $this->traceRequired = $trace; + } + + public function getXML($request) { + + $doc = new \DOMDocument('1.0'); + $doc->loadXML($request); + + $objWSSE = new \WSSESoap($doc); + $objWSSE->addTimestamp(); + + $objKey = new \XMLSecurityKey(\XMLSecurityKey::RSA_SHA256, ['type' => 'private']); + $objKey->loadKey($this->key, TRUE); + $objWSSE->signSoapDoc($objKey, ["algorithm" => \XMLSecurityDSig::SHA256]); + + $token = $objWSSE->addBinaryToken(file_get_contents($this->cert)); + $objWSSE->attachTokentoSig($token); + + return $objWSSE->saveXML(); + } + + public function getXMLforMethod($method, $data) { + $this->returnRequest = TRUE; + $this->$method($data); + $this->returnRequest = FALSE; + return $this->lastRequest; + } + + public function __doRequest($request, $location, $saction, $version, $one_way = NULL) { + + $xml = $this->getXML($request); + $this->lastRequest = $xml; + if ($this->returnRequest) { + return ''; + } + + $this->traceRequired && $this->lastResponseStartTime = microtime(TRUE); + + $response = $this->__doRequestByCurl($xml, $location, $saction, $version); + + $this->traceRequired && $this->lastResponseEndTime = microtime(TRUE); + + return $response; + } + + /** + * @param string $request + * @param string $location + * @param string $action + * @param int $version + * @param bool $one_way + * + * @return string|null + * @throws ClientException + */ + public function __doRequestByCurl($request, $location, $action, $version, $one_way = FALSE) + { + // Call via Curl and use the timeout a + $curl = curl_init($location); + if ($curl === false) { + throw new ClientException('Curl initialisation failed'); + } + /** @var $headers array of headers to be sent with request */ + $headers = array( + 'User-Agent: PHP-SOAP', + 'Content-Type: text/xml; charset=utf-8', + 'SOAPAction: "' . $action . '"', + 'Content-Length: ' . strlen($request), + ); + $options = array( + CURLOPT_VERBOSE => false, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $request, + CURLOPT_HEADER => $headers, + CURLOPT_HTTPHEADER => array(sprintf('Content-Type: %s', $version == 2 ? 'application/soap+xml' : 'text/xml'), sprintf('SOAPAction: %s', $action)), + ); + // Timeout in milliseconds + $options = $this->__curlSetTimeoutOption($options, $this->timeout, 'CURLOPT_TIMEOUT'); + // ConnectTimeout in milliseconds + $options = $this->__curlSetTimeoutOption($options, $this->connectTimeout, 'CURLOPT_CONNECTTIMEOUT'); + + $this->__setCurlOptions($curl, $options); + $response = curl_exec($curl); + + if (curl_errno($curl)) { + $errorMessage = curl_error($curl); + $errorNumber = curl_errno($curl); + curl_close($curl); + throw new ClientException($errorMessage, $errorNumber); + } + + $header_len = curl_getinfo($curl, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_len); + $body = substr($response, $header_len); + + curl_close($curl); + // Return? + if ($one_way) { + return null; + } else { + return $body; + } + } + + private function __setCurlOptions($curl, array $options) + { + foreach ($options as $option => $value) { + if (false !== curl_setopt($curl, $option, $value)) { + continue; + } + throw new ClientException( + sprintf('Failed setting CURL option %d (%s) to %s', $option, $this->__getCurlOptionName($option), var_export($value, true)) + ); + } + } + + private function __curlSetTimeoutOption($options, $milliseconds, $name) + { + if ($milliseconds > 0) { + if (defined("{$name}_MS")) { + $options[constant("{$name}_MS")] = $milliseconds; + } else { + $seconds = ceil($milliseconds / 1000); + $options[$name] = $seconds; + } + if ($milliseconds <= 1000) { + $options[CURLOPT_NOSIGNAL] = 1; + } + } + return $options; + } + + + /** + * + * @return float + */ + public function __getLastResponseTime() { + return $this->lastResponseEndTime - $this->lastResponseStartTime; + } + + /** + * + * @return float + */ + public function __getConnectionTime($tillLastRequest = FALSE) { + return $tillLastRequest ? $this->getConnectionTimeTillLastRequest() : $this->getConnectionTimeTillNow(); + } + + private function getConnectionTimeTillLastRequest() { + if (!$this->lastResponseEndTime || !$this->connectionStartTime) { + return NULL; + } + return $this->lastResponseEndTime - $this->connectionStartTime; + } + + private function getConnectionTimeTillNow() { + if (!$this->connectionStartTime) { + return NULL; + } + return microtime(TRUE) - $this->connectionStartTime; + } + + /** + * @return string + */ + public function __getLastRequest() { + return $this->lastRequest; + } + + /** + * @param int|null $milliseconds timeout in milliseconds + */ + public function setTimeout($milliseconds) + { + $this->timeout = $milliseconds; + } + /** + * @return int|null timeout in milliseconds + */ + public function getTimeout() + { + return $this->timeout; + } + /** + * @param int|null $milliseconds + */ + public function setConnectTimeout($milliseconds) + { + $this->connectTimeout = $milliseconds; + } + /** + * @return int|null + */ + public function getConnectTimeout() + { + return $this->connectTimeout; + } + + +} diff --git a/src/Utils/UUID.php b/src/Utils/UUID.php new file mode 100644 index 0000000..9674a43 --- /dev/null +++ b/src/Utils/UUID.php @@ -0,0 +1,32 @@ +