diff --git a/Install/Sms.sql b/Install/Sms.sql index 007ba35..ef7baa3 100644 --- a/Install/Sms.sql +++ b/Install/Sms.sql @@ -24,6 +24,8 @@ CREATE TABLE `cms_sms_operator` ( INSERT INTO `cms_sms_operator` (`id`, `name`, `tablename`, `remark`, `enable`) VALUES ('1', '阿里大于', 'alidayu', '阿里大于短信平台', '1'); INSERT INTO `cms_sms_operator` (`id`, `name`, `tablename`, `remark`, `enable`) VALUES ('2', '云之讯', 'ucpaas', '云之讯短信平台', '0'); +INSERT INTO `cms_sms_operator` (`id`, `name`, `tablename`, `remark`, `enable`) VALUES ('3', '阿里短信', 'alisms', '阿里短信服务', '0'); + DROP TABLE IF EXISTS `cms_sms_alidayu`; @@ -47,4 +49,18 @@ CREATE TABLE `cms_sms_ucpaas` ( `appid` VARCHAR(255) COMMENT '应用ID', `templateid` VARCHAR(255) COMMENT '短信模板ID', PRIMARY KEY (`id`) -) ENGINE = INNODB DEFAULT CHARSET = utf8; \ No newline at end of file +) ENGINE = INNODB DEFAULT CHARSET = utf8; + +DROP TABLE IF EXISTS `cms_sms_alisms`; + +CREATE TABLE `cms_sms_alisms` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `end_point` varchar(255) DEFAULT '' COMMENT '分公网跟私网(请根据业务自行选择)', + `access_id` varchar(255) DEFAULT '' COMMENT 'Access Key ID(阿里云API密钥)', + `access_key` varchar(255) DEFAULT '' COMMENT 'Access Key Secret(阿里云API密钥)', + `topic_name` varchar(255) DEFAULT '' COMMENT '短信主题名称', + `sign` varchar(255) DEFAULT '' COMMENT '短信签名', + `template` varchar(255) DEFAULT '' COMMENT '短信模版 Code', + `message_body` varchar(255) DEFAULT '' COMMENT 'SMS消息体(阿里没有说明作用,不为空即可)', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/Lib/Alisms/AliyunMNS/AsyncCallback.php b/Lib/Alisms/AliyunMNS/AsyncCallback.php new file mode 100644 index 0000000..47e43be --- /dev/null +++ b/Lib/Alisms/AliyunMNS/AsyncCallback.php @@ -0,0 +1,29 @@ +succeedCallback = $succeedCallback; + $this->failedCallback = $failedCallback; + } + + public function onSucceed(BaseResponse $result) + { + return call_user_func($this->succeedCallback, $result); + } + + public function onFailed(MnsException $e) + { + return call_user_func($this->failedCallback, $e); + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Client.php b/Lib/Alisms/AliyunMNS/Client.php new file mode 100644 index 0000000..da2f4b8 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Client.php @@ -0,0 +1,249 @@ +client = new HttpClient($endPoint, $accessId, + $accessKey, $securityToken, $config); + } + + /** + * Returns a queue reference for operating on the queue + * this function does not create the queue automatically. + * + * @param string $queueName: the queue name + * @param bool $base64: whether the message in queue will be base64 encoded + * + * @return Queue $queue: the Queue instance + */ + public function getQueueRef($queueName, $base64 = TRUE) + { + return new Queue($this->client, $queueName, $base64); + } + + /** + * Create Queue and Returns the Queue reference + * + * @param CreateQueueRequest $request: the QueueName and QueueAttributes + * + * @return CreateQueueResponse $response: the CreateQueueResponse + * + * @throws QueueAlreadyExistException if queue already exists + * @throws InvalidArgumentException if any argument value is invalid + * @throws MnsException if any other exception happends + */ + public function createQueue(CreateQueueRequest $request) + { + $response = new CreateQueueResponse($request->getQueueName()); + return $this->client->sendRequest($request, $response); + } + + /** + * Create Queue and Returns the Queue reference + * The request will not be sent until calling MnsPromise->wait(); + * + * @param CreateQueueRequest $request: the QueueName and QueueAttributes + * @param AsyncCallback $callback: the Callback when the request finishes + * + * @return MnsPromise $promise: the MnsPromise instance + * + * @throws MnsException if any exception happends + */ + public function createQueueAsync(CreateQueueRequest $request, + AsyncCallback $callback = NULL) + { + $response = new CreateQueueResponse($request->getQueueName()); + return $this->client->sendRequestAsync($request, $response, $callback); + } + + /** + * Query the queues created by current account + * + * @param ListQueueRequest $request: define filters for quering queues + * + * @return ListQueueResponse: the response containing queueNames + */ + public function listQueue(ListQueueRequest $request) + { + $response = new ListQueueResponse(); + return $this->client->sendRequest($request, $response); + } + + public function listQueueAsync(ListQueueRequest $request, + AsyncCallback $callback = NULL) + { + $response = new ListQueueResponse(); + return $this->client->sendRequestAsync($request, $response, $callback); + } + + /** + * Delete the specified queue + * the request will succeed even when the queue does not exist + * + * @param $queueName: the queueName + * + * @return DeleteQueueResponse + */ + public function deleteQueue($queueName) + { + $request = new DeleteQueueRequest($queueName); + $response = new DeleteQueueResponse(); + return $this->client->sendRequest($request, $response); + } + + public function deleteQueueAsync($queueName, + AsyncCallback $callback = NULL) + { + $request = new DeleteQueueRequest($queueName); + $response = new DeleteQueueResponse(); + return $this->client->sendRequestAsync($request, $response, $callback); + } + + // API for Topic + /** + * Returns a topic reference for operating on the topic + * this function does not create the topic automatically. + * + * @param string $topicName: the topic name + * + * @return Topic $topic: the Topic instance + */ + public function getTopicRef($topicName) + { + return new Topic($this->client, $topicName); + } + + /** + * Create Topic and Returns the Topic reference + * + * @param CreateTopicRequest $request: the TopicName and TopicAttributes + * + * @return CreateTopicResponse $response: the CreateTopicResponse + * + * @throws TopicAlreadyExistException if topic already exists + * @throws InvalidArgumentException if any argument value is invalid + * @throws MnsException if any other exception happends + */ + public function createTopic(CreateTopicRequest $request) + { + $response = new CreateTopicResponse($request->getTopicName()); + return $this->client->sendRequest($request, $response); + } + + /** + * Delete the specified topic + * the request will succeed even when the topic does not exist + * + * @param $topicName: the topicName + * + * @return DeleteTopicResponse + */ + public function deleteTopic($topicName) + { + $request = new DeleteTopicRequest($topicName); + $response = new DeleteTopicResponse(); + return $this->client->sendRequest($request, $response); + } + + /** + * Query the topics created by current account + * + * @param ListTopicRequest $request: define filters for quering topics + * + * @return ListTopicResponse: the response containing topicNames + */ + public function listTopic(ListTopicRequest $request) + { + $response = new ListTopicResponse(); + return $this->client->sendRequest($request, $response); + } + + /** + * Query the AccountAttributes + * + * @return GetAccountAttributesResponse: the response containing topicNames + * @throws MnsException if any exception happends + */ + public function getAccountAttributes() + { + $request = new GetAccountAttributesRequest(); + $response = new GetAccountAttributesResponse(); + return $this->client->sendRequest($request, $response); + } + + public function getAccountAttributesAsync(AsyncCallback $callback = NULL) + { + $request = new GetAccountAttributesRequest(); + $response = new GetAccountAttributesResponse(); + return $this->client->sendRequestAsync($request, $response, $callback); + } + + /** + * Set the AccountAttributes + * + * @param AccountAttributes $attributes: the AccountAttributes to set + * + * @return SetAccountAttributesResponse: the response + * + * @throws MnsException if any exception happends + */ + public function setAccountAttributes(AccountAttributes $attributes) + { + $request = new SetAccountAttributesRequest($attributes); + $response = new SetAccountAttributesResponse(); + return $this->client->sendRequest($request, $response); + } + + public function setAccountAttributesAsync(AccountAttributes $attributes, + AsyncCallback $callback = NULL) + { + $request = new SetAccountAttributesRequest($attributes); + $response = new SetAccountAttributesResponse(); + return $this->client->sendRequestAsync($request, $response, $callback); + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Common/XMLParser.php b/Lib/Alisms/AliyunMNS/Common/XMLParser.php new file mode 100644 index 0000000..9925995 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Common/XMLParser.php @@ -0,0 +1,51 @@ + NULL, 'Message' => NULL, 'RequestId' => NULL, 'HostId' => NULL); + while ($xmlReader->Read()) + { + if ($xmlReader->nodeType == \XMLReader::ELEMENT) + { + switch ($xmlReader->name) { + case 'Code': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $result['Code'] = $xmlReader->value; + } + break; + case 'Message': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $result['Message'] = $xmlReader->value; + } + break; + case 'RequestId': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $result['RequestId'] = $xmlReader->value; + } + break; + case 'HostId': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $result['HostId'] = $xmlReader->value; + } + break; + } + } + } + return $result; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Config.php b/Lib/Alisms/AliyunMNS/Config.php new file mode 100644 index 0000000..4898fcd --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Config.php @@ -0,0 +1,74 @@ +maxAttempts = 3; + $this->proxy = NULL; + $this->requestTimeout = 35; // 35 seconds + $this->connectTimeout = 3; // 3 seconds + $this->expectContinue = false; + } + + /* + public function getMaxAttempts() + { + return $this->maxAttempts; + } + + public function setMaxAttempts($maxAttempts) + { + $this->maxAttempts = $maxAttempts; + } + */ + + public function getProxy() + { + return $this->proxy; + } + + public function setProxy($proxy) + { + $this->proxy = $proxy; + } + + public function getRequestTimeout() + { + return $this->requestTimeout; + } + + public function setRequestTimeout($requestTimeout) + { + $this->requestTimeout = $requestTimeout; + } + + public function setConnectTimeout($connectTimeout) + { + $this->connectTimeout = $connectTimeout; + } + + public function getConnectTimeout() + { + return $this->connectTimeout; + } + + public function getExpectContinue() + { + return $this->expectContinue; + } + + public function setExpectContinue($expectContinue) + { + $this->expectContinue = $expectContinue; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Constants.php b/Lib/Alisms/AliyunMNS/Constants.php new file mode 100644 index 0000000..3cd8afa --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Constants.php @@ -0,0 +1,76 @@ + diff --git a/Lib/Alisms/AliyunMNS/Exception/BatchDeleteFailException.php b/Lib/Alisms/AliyunMNS/Exception/BatchDeleteFailException.php new file mode 100644 index 0000000..8bc987c --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Exception/BatchDeleteFailException.php @@ -0,0 +1,35 @@ +deleteMessageErrorItems = array(); + } + + public function addDeleteMessageErrorItem(DeleteMessageErrorItem $item) + { + $this->deleteMessageErrorItems[] = $item; + } + + public function getDeleteMessageErrorItems() + { + return $this->deleteMessageErrorItems; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Exception/BatchSendFailException.php b/Lib/Alisms/AliyunMNS/Exception/BatchSendFailException.php new file mode 100644 index 0000000..da460f9 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Exception/BatchSendFailException.php @@ -0,0 +1,35 @@ +sendMessageResponseItems = array(); + } + + public function addSendMessageResponseItem(SendMessageResponseItem $item) + { + $this->sendMessageResponseItems[] = $item; + } + + public function getSendMessageResponseItems() + { + return $this->sendMessageResponseItems; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Exception/InvalidArgumentException.php b/Lib/Alisms/AliyunMNS/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..b4b8caf --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Exception/InvalidArgumentException.php @@ -0,0 +1,10 @@ + diff --git a/Lib/Alisms/AliyunMNS/Exception/MalformedXMLException.php b/Lib/Alisms/AliyunMNS/Exception/MalformedXMLException.php new file mode 100644 index 0000000..21ebc34 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Exception/MalformedXMLException.php @@ -0,0 +1,10 @@ + diff --git a/Lib/Alisms/AliyunMNS/Exception/MessageNotExistException.php b/Lib/Alisms/AliyunMNS/Exception/MessageNotExistException.php new file mode 100644 index 0000000..f57e404 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Exception/MessageNotExistException.php @@ -0,0 +1,10 @@ + diff --git a/Lib/Alisms/AliyunMNS/Exception/MnsException.php b/Lib/Alisms/AliyunMNS/Exception/MnsException.php new file mode 100644 index 0000000..17f02e6 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Exception/MnsException.php @@ -0,0 +1,65 @@ += 500) + { + $mnsErrorCode = "ServerError"; + } + else + { + $mnsErrorCode = "ClientError"; + } + } + $this->mnsErrorCode = $mnsErrorCode; + + $this->requestId = $requestId; + $this->hostId = $hostId; + } + + public function __toString() + { + $str = "Code: " . $this->getCode() . " Message: " . $this->getMessage(); + if ($this->mnsErrorCode != NULL) + { + $str .= " MnsErrorCode: " . $this->mnsErrorCode; + } + if ($this->requestId != NULL) + { + $str .= " RequestId: " . $this->requestId; + } + if ($this->hostId != NULL) + { + $str .= " HostId: " . $this->hostId; + } + return $str; + } + + public function getMnsErrorCode() + { + return $this->mnsErrorCode; + } + + public function getRequestId() + { + return $this->requestId; + } + + public function getHostId() + { + return $this->hostId; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Exception/QueueAlreadyExistException.php b/Lib/Alisms/AliyunMNS/Exception/QueueAlreadyExistException.php new file mode 100644 index 0000000..4fbf87c --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Exception/QueueAlreadyExistException.php @@ -0,0 +1,10 @@ + diff --git a/Lib/Alisms/AliyunMNS/Exception/QueueNotExistException.php b/Lib/Alisms/AliyunMNS/Exception/QueueNotExistException.php new file mode 100644 index 0000000..ac176de --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Exception/QueueNotExistException.php @@ -0,0 +1,10 @@ + diff --git a/Lib/Alisms/AliyunMNS/Exception/ReceiptHandleErrorException.php b/Lib/Alisms/AliyunMNS/Exception/ReceiptHandleErrorException.php new file mode 100644 index 0000000..352377e --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Exception/ReceiptHandleErrorException.php @@ -0,0 +1,10 @@ + diff --git a/Lib/Alisms/AliyunMNS/Exception/SubscriptionAlreadyExistException.php b/Lib/Alisms/AliyunMNS/Exception/SubscriptionAlreadyExistException.php new file mode 100644 index 0000000..828ba40 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Exception/SubscriptionAlreadyExistException.php @@ -0,0 +1,10 @@ + diff --git a/Lib/Alisms/AliyunMNS/Exception/SubscriptionNotExistException.php b/Lib/Alisms/AliyunMNS/Exception/SubscriptionNotExistException.php new file mode 100644 index 0000000..f3397ff --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Exception/SubscriptionNotExistException.php @@ -0,0 +1,9 @@ + diff --git a/Lib/Alisms/AliyunMNS/Exception/TopicAlreadyExistException.php b/Lib/Alisms/AliyunMNS/Exception/TopicAlreadyExistException.php new file mode 100644 index 0000000..5adc8ed --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Exception/TopicAlreadyExistException.php @@ -0,0 +1,10 @@ + diff --git a/Lib/Alisms/AliyunMNS/Exception/TopicNotExistException.php b/Lib/Alisms/AliyunMNS/Exception/TopicNotExistException.php new file mode 100644 index 0000000..f92a5d3 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Exception/TopicNotExistException.php @@ -0,0 +1,10 @@ + diff --git a/Lib/Alisms/AliyunMNS/Http/HttpClient.php b/Lib/Alisms/AliyunMNS/Http/HttpClient.php new file mode 100644 index 0000000..4d48858 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Http/HttpClient.php @@ -0,0 +1,167 @@ +accessId = $accessId; + $this->accessKey = $accessKey; + $this->client = new \GuzzleHttp\Client([ + 'base_uri' => $endPoint, + 'defaults' => [ + 'headers' => [ + 'Host' => $endPoint + ], + 'proxy' => $config->getProxy(), + 'expect' => $config->getExpectContinue() + ] + ]); + $this->requestTimeout = $config->getRequestTimeout(); + $this->connectTimeout = $config->getConnectTimeout(); + $this->securityToken = $securityToken; + $this->endpoint = $endPoint; + $this->parseEndpoint(); + } + + public function getRegion() + { + return $this->region; + } + + public function getAccountId() + { + return $this->accountId; + } + + // This function is for SDK internal use + private function parseEndpoint() + { + $pieces = explode("//", $this->endpoint); + $host = end($pieces); + + $host_pieces = explode(".", $host); + $this->accountId = $host_pieces[0]; + $region_pieces = explode("-internal", $host_pieces[2]); + $this->region = $region_pieces[0]; + } + + private function addRequiredHeaders(BaseRequest &$request) + { + $body = $request->generateBody(); + $queryString = $request->generateQueryString(); + + $request->setBody($body); + $request->setQueryString($queryString); + + if ($body != NULL) + { + $request->setHeader(Constants::CONTENT_LENGTH, strlen($body)); + } + $request->setHeader('Date', gmdate(Constants::GMT_DATE_FORMAT)); + if (!$request->isHeaderSet(Constants::CONTENT_TYPE)) + { + $request->setHeader(Constants::CONTENT_TYPE, 'text/xml'); + } + $request->setHeader(Constants::MNS_VERSION_HEADER, Constants::MNS_VERSION); + + if ($this->securityToken != NULL) + { + $request->setHeader(Constants::SECURITY_TOKEN, $this->securityToken); + } + + $sign = Signature::SignRequest($this->accessKey, $request); + $request->setHeader(Constants::AUTHORIZATION, + Constants::MNS . " " . $this->accessId . ":" . $sign); + } + + public function sendRequestAsync(BaseRequest $request, + BaseResponse &$response, AsyncCallback $callback = NULL) + { + $promise = $this->sendRequestAsyncInternal($request, $response, $callback); + return new MnsPromise($promise, $response); + } + + public function sendRequest(BaseRequest $request, BaseResponse &$response) + { + $promise = $this->sendRequestAsync($request, $response); + return $promise->wait(); + } + + private function sendRequestAsyncInternal(BaseRequest &$request, BaseResponse &$response, AsyncCallback $callback = NULL) + { + $this->addRequiredHeaders($request); + + $parameters = array('exceptions' => false, 'http_errors' => false); + $queryString = $request->getQueryString(); + $body = $request->getBody(); + if ($queryString != NULL) { + $parameters['query'] = $queryString; + } + if ($body != NULL) { + $parameters['body'] = $body; + } + + $parameters['timeout'] = $this->requestTimeout; + $parameters['connect_timeout'] = $this->connectTimeout; + + $request = new Request(strtoupper($request->getMethod()), + $request->getResourcePath(), $request->getHeaders()); + try + { + if ($callback != NULL) + { + return $this->client->sendAsync($request, $parameters)->then( + function ($res) use (&$response, $callback) { + try { + $response->parseResponse($res->getStatusCode(), $res->getBody()); + $callback->onSucceed($response); + } catch (MnsException $e) { + $callback->onFailed($e); + } + } + ); + } + else + { + return $this->client->sendAsync($request, $parameters); + } + } + catch (TransferException $e) + { + $message = $e->getMessage(); + if ($e->hasResponse()) { + $message = $e->getResponse()->getBody(); + } + throw new MnsException($e->getCode(), $message, $e); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Model/AccountAttributes.php b/Lib/Alisms/AliyunMNS/Model/AccountAttributes.php new file mode 100644 index 0000000..8127b83 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Model/AccountAttributes.php @@ -0,0 +1,64 @@ +loggingBucket = $loggingBucket; + } + + public function setLoggingBucket($loggingBucket) + { + $this->loggingBucket = $loggingBucket; + } + + public function getLoggingBucket() + { + return $this->loggingBucket; + } + + public function writeXML(\XMLWriter $xmlWriter) + { + if ($this->loggingBucket !== NULL) + { + $xmlWriter->writeElement(Constants::LOGGING_BUCKET, $this->loggingBucket); + } + } + + static public function fromXML(\XMLReader $xmlReader) + { + $loggingBucket = NULL; + + while ($xmlReader->read()) + { + if ($xmlReader->nodeType == \XMLReader::ELEMENT) + { + switch ($xmlReader->name) { + case 'LoggingBucket': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $loggingBucket = $xmlReader->value; + } + break; + } + } + } + + $attributes = new AccountAttributes($loggingBucket); + return $attributes; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Model/BatchSmsAttributes.php b/Lib/Alisms/AliyunMNS/Model/BatchSmsAttributes.php new file mode 100644 index 0000000..38b9da7 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Model/BatchSmsAttributes.php @@ -0,0 +1,97 @@ +freeSignName = $freeSignName; + $this->templateCode = $templateCode; + $this->smsParams = $smsParams; + } + + public function setFreeSignName($freeSignName) + { + $this->freeSignName = $freeSignName; + } + + public function getFreeSignName() + { + return $this->freeSignName; + } + + public function setTemplateCode($templateCode) + { + $this->templateCode = $templateCode; + } + + public function getTemplateCode() + { + return $this->templateCode; + } + + public function addReceiver($phone, $params) + { + if (!is_array($params)) + { + throw new MnsException(400, "Params Should be Array!"); + } + + if ($this->smsParams == null) + { + $this->smsParams = array(); + } + + $this->smsParams[$phone] = $params; + } + + public function getSmsParams() + { + return $this->smsParams; + } + + public function writeXML(\XMLWriter $xmlWriter) + { + $jsonArray = array("Type" => "multiContent"); + if ($this->freeSignName !== NULL) + { + $jsonArray[Constants::FREE_SIGN_NAME] = $this->freeSignName; + } + if ($this->templateCode !== NULL) + { + $jsonArray[Constants::TEMPLATE_CODE] = $this->templateCode; + } + + if ($this->smsParams != null) + { + if (!is_array($this->smsParams)) + { + throw new MnsException(400, "SmsParams should be an array!"); + } + if (!empty($this->smsParams)) + { + $jsonArray[Constants::SMS_PARAMS] = json_encode($this->smsParams, JSON_FORCE_OBJECT); + } + } + + if (!empty($jsonArray)) + { + $xmlWriter->writeElement(Constants::DIRECT_SMS, json_encode($jsonArray)); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Model/DeleteMessageErrorItem.php b/Lib/Alisms/AliyunMNS/Model/DeleteMessageErrorItem.php new file mode 100644 index 0000000..137a262 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Model/DeleteMessageErrorItem.php @@ -0,0 +1,83 @@ +errorCode = $errorCode; + $this->errorMessage = $errorMessage; + $this->receiptHandle = $receiptHandle; + } + + public function getErrorCode() + { + return $this->errorCode; + } + + public function getErrorMessage() + { + return $this->errorMessage; + } + + public function getReceiptHandle() + { + return $this->receiptHandle; + } + + static public function fromXML($xmlReader) + { + $errorCode = NULL; + $errorMessage = NULL; + $receiptHandle = NULL; + + while ($xmlReader->read()) + { + switch ($xmlReader->nodeType) + { + case \XMLReader::ELEMENT: + switch ($xmlReader->name) + { + case Constants::ERROR_CODE: + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $errorCode = $xmlReader->value; + } + break; + case Constants::ERROR_MESSAGE: + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $errorMessage = $xmlReader->value; + } + break; + case Constants::RECEIPT_HANDLE: + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $receiptHandle = $xmlReader->value; + } + break; + } + break; + case \XMLReader::END_ELEMENT: + if ($xmlReader->name == Constants::ERROR) + { + return new DeleteMessageErrorItem($errorCode, $errorMessage, $receiptHandle); + } + break; + } + } + + return new DeleteMessageErrorItem($errorCode, $errorMessage, $receiptHandle); + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Model/MailAttributes.php b/Lib/Alisms/AliyunMNS/Model/MailAttributes.php new file mode 100644 index 0000000..bb74170 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Model/MailAttributes.php @@ -0,0 +1,128 @@ +subject = $subject; + $this->accountName = $accountName; + $this->addressType = $addressType; + $this->replyToAddress = $replyToAddress; + $this->isHtml = $isHtml; + } + + public function setSubject($subject) + { + $this->subject = $subject; + } + + public function getSubject() + { + return $this->subject; + } + + public function setAccountName($accountName) + { + $this->accountName = $accountName; + } + + public function getAccountName() + { + return $this->accountName; + } + + public function setAddressType($addressType) + { + $this->addressType = $addressType; + } + + public function getAddressType() + { + return $this->addressType; + } + + public function setReplyToAddress($replyToAddress) + { + $this->replyToAddress = $replyToAddress; + } + + public function getReplyToAddress() + { + return $this->replyToAddress; + } + + public function setIsHtml($isHtml) + { + $this->isHtml = $isHtml; + } + + public function getIsHtml() + { + return $this->isHtml; + } + + public function writeXML(\XMLWriter $xmlWriter) + { + $jsonArray = array(); + if ($this->subject !== NULL) + { + $jsonArray[Constants::SUBJECT] = $this->subject; + } + if ($this->accountName !== NULL) + { + $jsonArray[Constants::ACCOUNT_NAME] = $this->accountName; + } + if ($this->addressType !== NULL) + { + $jsonArray[Constants::ADDRESS_TYPE] = $this->addressType; + } + else + { + $jsonArray[Constants::ADDRESS_TYPE] = 0; + } + if ($this->replyToAddress !== NULL) + { + if ($this->replyToAddress === TRUE) + { + $jsonArray[Constants::REPLY_TO_ADDRESS] = "1"; + } + else + { + $jsonArray[Constants::REPLY_TO_ADDRESS] = "0"; + } + } + if ($this->isHtml !== NULL) + { + if ($this->isHtml === TRUE) + { + $jsonArray[Constants::IS_HTML] = "1"; + } + else + { + $jsonArray[Constants::IS_HTML] = "0"; + } + } + + if (!empty($jsonArray)) + { + $xmlWriter->writeElement(Constants::DIRECT_MAIL, json_encode($jsonArray)); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Model/Message.php b/Lib/Alisms/AliyunMNS/Model/Message.php new file mode 100644 index 0000000..5dc67ac --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Model/Message.php @@ -0,0 +1,145 @@ +messageId = $messageId; + $this->messageBodyMD5 = $messageBodyMD5; + $this->messageBody = $messageBody; + $this->enqueueTime = $enqueueTime; + $this->nextVisibleTime = $nextVisibleTime; + $this->firstDequeueTime = $firstDequeueTime; + $this->dequeueCount = $dequeueCount; + $this->priority = $priority; + $this->receiptHandle = $receiptHandle; + } + + static public function fromXML(\XMLReader $xmlReader, $base64) + { + $messageId = NULL; + $messageBodyMD5 = NULL; + $messageBody = NULL; + $enqueueTime = NULL; + $nextVisibleTime = NULL; + $firstDequeueTime = NULL; + $dequeueCount = NULL; + $priority = NULL; + $receiptHandle = NULL; + + while ($xmlReader->read()) + { + switch ($xmlReader->nodeType) + { + case \XMLReader::ELEMENT: + switch ($xmlReader->name) { + case Constants::MESSAGE_ID: + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $messageId = $xmlReader->value; + } + break; + case Constants::MESSAGE_BODY_MD5: + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $messageBodyMD5 = $xmlReader->value; + } + break; + case Constants::MESSAGE_BODY: + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + if ($base64 == TRUE) { + $messageBody = base64_decode($xmlReader->value); + } else { + $messageBody = $xmlReader->value; + } + } + break; + case Constants::ENQUEUE_TIME: + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $enqueueTime = $xmlReader->value; + } + break; + case Constants::NEXT_VISIBLE_TIME: + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $nextVisibleTime = $xmlReader->value; + } + break; + case Constants::FIRST_DEQUEUE_TIME: + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $firstDequeueTime = $xmlReader->value; + } + break; + case Constants::DEQUEUE_COUNT: + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $dequeueCount = $xmlReader->value; + } + break; + case Constants::PRIORITY: + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $priority = $xmlReader->value; + } + break; + case Constants::RECEIPT_HANDLE: + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $receiptHandle = $xmlReader->value; + } + break; + } + break; + case \XMLReader::END_ELEMENT: + if ($xmlReader->name == 'Message') + { + $message = new Message( + $messageId, + $messageBodyMD5, + $messageBody, + $enqueueTime, + $nextVisibleTime, + $firstDequeueTime, + $dequeueCount, + $priority, + $receiptHandle); + return $message; + } + break; + } + } + + $message = new Message( + $messageId, + $messageBodyMD5, + $messageBody, + $enqueueTime, + $nextVisibleTime, + $firstDequeueTime, + $dequeueCount, + $priority, + $receiptHandle); + + return $message; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Model/MessageAttributes.php b/Lib/Alisms/AliyunMNS/Model/MessageAttributes.php new file mode 100644 index 0000000..d676c7e --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Model/MessageAttributes.php @@ -0,0 +1,53 @@ +attributes = $attributes; + } + + public function setAttributes($attributes) + { + $this->attributes = $attributes; + } + + public function getAttributes() + { + return $this->attributes; + } + + public function writeXML(\XMLWriter $xmlWriter) + { + $xmlWriter->startELement(Constants::MESSAGE_ATTRIBUTES); + if ($this->attributes != NULL) + { + if (is_array($this->attributes)) + { + foreach ($this->attributes as $subAttributes) + { + $subAttributes->writeXML($xmlWriter); + } + } + else + { + $this->attributes->writeXML($xmlWriter); + } + } + $xmlWriter->endElement(); + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Model/QueueAttributes.php b/Lib/Alisms/AliyunMNS/Model/QueueAttributes.php new file mode 100644 index 0000000..ae9b7ec --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Model/QueueAttributes.php @@ -0,0 +1,308 @@ +delaySeconds = $delaySeconds; + $this->maximumMessageSize = $maximumMessageSize; + $this->messageRetentionPeriod = $messageRetentionPeriod; + $this->visibilityTimeout = $visibilityTimeout; + $this->pollingWaitSeconds = $pollingWaitSeconds; + $this->loggingEnabled = $LoggingEnabled; + + $this->queueName = $queueName; + $this->createTime = $createTime; + $this->lastModifyTime = $lastModifyTime; + $this->activeMessages = $activeMessages; + $this->inactiveMessages = $inactiveMessages; + $this->delayMessages = $delayMessages; + } + + public function setDelaySeconds($delaySeconds) + { + $this->delaySeconds = $delaySeconds; + } + + public function getDelaySeconds() + { + return $this->delaySeconds; + } + + public function setLoggingEnabled($loggingEnabled) + { + $this->loggingEnabled = $loggingEnabled; + } + + public function getLoggingEnabled() + { + return $this->loggingEnabled; + } + + public function setMaximumMessageSize($maximumMessageSize) + { + $this->maximumMessageSize = $maximumMessageSize; + } + + public function getMaximumMessageSize() + { + return $this->maximumMessageSize; + } + + public function setMessageRetentionPeriod($messageRetentionPeriod) + { + $this->messageRetentionPeriod = $messageRetentionPeriod; + } + + public function getMessageRetentionPeriod() + { + return $this->messageRetentionPeriod; + } + + public function setVisibilityTimeout($visibilityTimeout) + { + $this->visibilityTimeout = $visibilityTimeout; + } + + public function getVisibilityTimeout() + { + return $this->visibilityTimeout; + } + + public function setPollingWaitSeconds($pollingWaitSeconds) + { + $this->pollingWaitSeconds = $pollingWaitSeconds; + } + + public function getPollingWaitSeconds() + { + return $this->pollingWaitSeconds; + } + + public function getQueueName() + { + return $this->queueName; + } + + public function getCreateTime() + { + return $this->createTime; + } + + public function getLastModifyTime() + { + return $this->lastModifyTime; + } + + public function getActiveMessages() + { + return $this->activeMessages; + } + + public function getInactiveMessages() + { + return $this->inactiveMessages; + } + + public function getDelayMessages() + { + return $this->delayMessages; + } + + public function writeXML(\XMLWriter $xmlWriter) + { + if ($this->delaySeconds != NULL) + { + $xmlWriter->writeElement(Constants::DELAY_SECONDS, $this->delaySeconds); + } + if ($this->maximumMessageSize != NULL) + { + $xmlWriter->writeElement(Constants::MAXIMUM_MESSAGE_SIZE, $this->maximumMessageSize); + } + if ($this->messageRetentionPeriod != NULL) + { + $xmlWriter->writeElement(Constants::MESSAGE_RETENTION_PERIOD, $this->messageRetentionPeriod); + } + if ($this->visibilityTimeout != NULL) + { + $xmlWriter->writeElement(Constants::VISIBILITY_TIMEOUT, $this->visibilityTimeout); + } + if ($this->pollingWaitSeconds != NULL) + { + $xmlWriter->writeElement(Constants::POLLING_WAIT_SECONDS, $this->pollingWaitSeconds); + } + if ($this->loggingEnabled !== NULL) + { + $xmlWriter->writeElement(Constants::LOGGING_ENABLED, $this->loggingEnabled ? "True" : "False"); + } + } + + static public function fromXML(\XMLReader $xmlReader) + { + $delaySeconds = NULL; + $maximumMessageSize = NULL; + $messageRetentionPeriod = NULL; + $visibilityTimeout = NULL; + $pollingWaitSeconds = NULL; + $queueName = NULL; + $createTime = NULL; + $lastModifyTime = NULL; + $activeMessages = NULL; + $inactiveMessages = NULL; + $delayMessages = NULL; + $loggingEnabled = NULL; + + while ($xmlReader->read()) + { + if ($xmlReader->nodeType == \XMLReader::ELEMENT) + { + switch ($xmlReader->name) { + case 'DelaySeconds': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $delaySeconds = $xmlReader->value; + } + break; + case 'MaximumMessageSize': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $maximumMessageSize = $xmlReader->value; + } + break; + case 'MessageRetentionPeriod': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $messageRetentionPeriod = $xmlReader->value; + } + break; + case 'VisibilityTimeout': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $visibilityTimeout = $xmlReader->value; + } + break; + case 'PollingWaitSeconds': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $pollingWaitSeconds = $xmlReader->value; + } + break; + case 'QueueName': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $queueName = $xmlReader->value; + } + break; + case 'CreateTime': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $createTime = $xmlReader->value; + } + break; + case 'LastModifyTime': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $lastModifyTime = $xmlReader->value; + } + break; + case 'ActiveMessages': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $activeMessages = $xmlReader->value; + } + break; + case 'InactiveMessages': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $inactiveMessages = $xmlReader->value; + } + break; + case 'DelayMessages': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $delayMessages = $xmlReader->value; + } + break; + case 'LoggingEnabled': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $loggingEnabled = $xmlReader->value; + if ($loggingEnabled == "True") + { + $loggingEnabled = True; + } + else + { + $loggingEnabled = False; + } + } + break; + } + } + } + + $attributes = new QueueAttributes( + $delaySeconds, + $maximumMessageSize, + $messageRetentionPeriod, + $visibilityTimeout, + $pollingWaitSeconds, + $queueName, + $createTime, + $lastModifyTime, + $activeMessages, + $inactiveMessages, + $delayMessages, + $loggingEnabled); + return $attributes; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Model/SendMessageRequestItem.php b/Lib/Alisms/AliyunMNS/Model/SendMessageRequestItem.php new file mode 100644 index 0000000..66deb16 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Model/SendMessageRequestItem.php @@ -0,0 +1,27 @@ +messageBody = $messageBody; + $this->delaySeconds = $delaySeconds; + $this->priority = $priority; + } + + public function writeXML(\XMLWriter $xmlWriter, $base64) + { + $xmlWriter->startELement('Message'); + $this->writeMessagePropertiesForSendXML($xmlWriter, $base64); + $xmlWriter->endElement(); + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Model/SendMessageResponseItem.php b/Lib/Alisms/AliyunMNS/Model/SendMessageResponseItem.php new file mode 100644 index 0000000..20e4cbb --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Model/SendMessageResponseItem.php @@ -0,0 +1,119 @@ +isSucceed = $isSucceed; + if ($isSucceed == TRUE) + { + $this->messageId = $param1; + $this->messageBodyMD5 = $param2; + } + else + { + $this->errorCode = $param1; + $this->errorMessage = $param2; + } + } + + public function isSucceed() + { + return $this->isSucceed; + } + + public function getErrorCode() + { + return $this->errorCode; + } + + public function getErrorMessage() + { + return $this->errorMessage; + } + + static public function fromXML($xmlReader) + { + $messageId = NULL; + $messageBodyMD5 = NULL; + $errorCode = NULL; + $errorMessage = NULL; + + while ($xmlReader->read()) + { + switch ($xmlReader->nodeType) + { + case \XMLReader::ELEMENT: + switch ($xmlReader->name) { + case Constants::MESSAGE_ID: + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $messageId = $xmlReader->value; + } + break; + case Constants::MESSAGE_BODY_MD5: + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $messageBodyMD5 = $xmlReader->value; + } + break; + case Constants::ERROR_CODE: + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $errorCode = $xmlReader->value; + } + break; + case Constants::ERROR_MESSAGE: + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $errorMessage = $xmlReader->value; + } + break; + } + break; + case \XMLReader::END_ELEMENT: + if ($xmlReader->name == 'Message') + { + if ($messageId != NULL) + { + return new SendMessageResponseItem(TRUE, $messageId, $messageBodyMD5); + } + else + { + return new SendMessageResponseItem(FALSE, $errorCode, $errorMessage); + } + } + break; + } + } + + if ($messageId != NULL) + { + return new SendMessageResponseItem(TRUE, $messageId, $messageBodyMD5); + } + else + { + return new SendMessageResponseItem(FALSE, $errorCode, $errorMessage); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Model/SmsAttributes.php b/Lib/Alisms/AliyunMNS/Model/SmsAttributes.php new file mode 100644 index 0000000..025b084 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Model/SmsAttributes.php @@ -0,0 +1,100 @@ +freeSignName = $freeSignName; + $this->templateCode = $templateCode; + $this->smsParams = $smsParams; + $this->receiver = $receiver; + } + + public function setFreeSignName($freeSignName) + { + $this->freeSignName = $freeSignName; + } + + public function getFreeSignName() + { + return $this->freeSignName; + } + + public function setTemplateCode($templateCode) + { + $this->templateCode = $templateCode; + } + + public function getTemplateCode() + { + return $this->templateCode; + } + + public function setSmsParams($smsParams) + { + $this->smsParams = $smsParams; + } + + public function getSmsParams() + { + return $this->smsParams; + } + + public function setReceiver($receiver) + { + $this->receiver = $receiver; + } + + public function getReceiver() + { + return $this->receiver; + } + + public function writeXML(\XMLWriter $xmlWriter) + { + $jsonArray = array(); + if ($this->freeSignName !== NULL) + { + $jsonArray[Constants::FREE_SIGN_NAME] = $this->freeSignName; + } + if ($this->templateCode !== NULL) + { + $jsonArray[Constants::TEMPLATE_CODE] = $this->templateCode; + } + if ($this->receiver !== NULL) + { + $jsonArray[Constants::RECEIVER] = $this->receiver; + } + + if ($this->smsParams !== null) + { + if (!is_array($this->smsParams)) + { + throw new MnsException(400, "SmsParams should be an array!"); + } + $jsonArray[Constants::SMS_PARAMS] = json_encode($this->smsParams, JSON_FORCE_OBJECT); + } + + if (!empty($jsonArray)) + { + $xmlWriter->writeElement(Constants::DIRECT_SMS, json_encode($jsonArray)); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Model/SubscriptionAttributes.php b/Lib/Alisms/AliyunMNS/Model/SubscriptionAttributes.php new file mode 100644 index 0000000..e8eb534 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Model/SubscriptionAttributes.php @@ -0,0 +1,207 @@ +endpoint = $endpoint; + $this->strategy = $strategy; + $this->contentFormat = $contentFormat; + $this->subscriptionName = $subscriptionName; + + //cloud change in AliyunMNS\Topic + $this->topicName = $topicName; + + $this->topicOwner = $topicOwner; + $this->createTime = $createTime; + $this->lastModifyTime = $lastModifyTime; + } + + public function getEndpoint() + { + return $this->endpoint; + } + + public function setEndpoint($endpoint) + { + $this->endpoint = $endpoint; + } + + public function getStrategy() + { + return $this->strategy; + } + + public function setStrategy($strategy) + { + $this->strategy = $strategy; + } + + public function getContentFormat() + { + return $this->contentFormat; + } + + public function setContentFormat($contentFormat) + { + $this->contentFormat = $contentFormat; + } + + public function getTopicName() + { + return $this->topicName; + } + + public function setTopicName($topicName) + { + $this->topicName = $topicName; + } + + public function getTopicOwner() + { + return $this->topicOwner; + } + + public function getSubscriptionName() + { + return $this->subscriptionName; + } + + public function getCreateTime() + { + return $this->createTime; + } + + public function getLastModifyTime() + { + return $this->lastModifyTime; + } + + public function writeXML(\XMLWriter $xmlWriter) + { + if ($this->endpoint != NULL) + { + $xmlWriter->writeElement(Constants::ENDPOINT, $this->endpoint); + } + if ($this->strategy != NULL) + { + $xmlWriter->writeElement(Constants::STRATEGY, $this->strategy); + } + if ($this->contentFormat != NULL) + { + $xmlWriter->writeElement(Constants::CONTENT_FORMAT, $this->contentFormat); + } + } + + static public function fromXML(\XMLReader $xmlReader) + { + $endpoint = NULL; + $strategy = NULL; + $contentFormat = NULL; + $topicOwner = NULL; + $topicName = NULL; + $createTime = NULL; + $lastModifyTime = NULL; + + while ($xmlReader->read()) + { + if ($xmlReader->nodeType == \XMLReader::ELEMENT) + { + switch ($xmlReader->name) { + case 'TopicOwner': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $topicOwner = $xmlReader->value; + } + break; + case 'TopicName': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $topicName = $xmlReader->value; + } + break; + case 'SubscriptionName': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $subscriptionName = $xmlReader->value; + } + case 'Endpoint': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $endpoint = $xmlReader->value; + } + break; + case 'NotifyStrategy': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $strategy = $xmlReader->value; + } + break; + case 'NotifyContentFormat': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $contentFormat = $xmlReader->value; + } + break; + case 'CreateTime': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $createTime = $xmlReader->value; + } + break; + case 'LastModifyTime': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $lastModifyTime = $xmlReader->value; + } + break; + } + } + } + + $attributes = new SubscriptionAttributes( + $subscriptionName, + $endpoint, + $strategy, + $contentFormat, + $topicName, + $topicOwner, + $createTime, + $lastModifyTime); + return $attributes; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Model/TopicAttributes.php b/Lib/Alisms/AliyunMNS/Model/TopicAttributes.php new file mode 100644 index 0000000..e7d5c11 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Model/TopicAttributes.php @@ -0,0 +1,179 @@ +maximumMessageSize = $maximumMessageSize; + $this->messageRetentionPeriod = $messageRetentionPeriod; + $this->loggingEnabled = $LoggingEnabled; + + $this->topicName = $topicName; + $this->createTime = $createTime; + $this->lastModifyTime = $lastModifyTime; + } + + public function setMaximumMessageSize($maximumMessageSize) + { + $this->maximumMessageSize = $maximumMessageSize; + } + + public function getMaximumMessageSize() + { + return $this->maximumMessageSize; + } + + public function setLoggingEnabled($loggingEnabled) + { + $this->loggingEnabled = $loggingEnabled; + } + + public function getLoggingEnabled() + { + return $this->loggingEnabled; + } + + public function setMessageRetentionPeriod($messageRetentionPeriod) + { + $this->messageRetentionPeriod = $messageRetentionPeriod; + } + + public function getMessageRetentionPeriod() + { + return $this->messageRetentionPeriod; + } + + public function getTopicName() + { + return $this->topicName; + } + + public function getCreateTime() + { + return $this->createTime; + } + + public function getLastModifyTime() + { + return $this->lastModifyTime; + } + + public function writeXML(\XMLWriter $xmlWriter) + { + if ($this->maximumMessageSize != NULL) + { + $xmlWriter->writeElement(Constants::MAXIMUM_MESSAGE_SIZE, $this->maximumMessageSize); + } + if ($this->messageRetentionPeriod != NULL) + { + $xmlWriter->writeElement(Constants::MESSAGE_RETENTION_PERIOD, $this->messageRetentionPeriod); + } + if ($this->loggingEnabled !== NULL) + { + $xmlWriter->writeElement(Constants::LOGGING_ENABLED, $this->loggingEnabled ? "True" : "False"); + } + } + + static public function fromXML(\XMLReader $xmlReader) + { + $maximumMessageSize = NULL; + $messageRetentionPeriod = NULL; + $topicName = NULL; + $createTime = NULL; + $lastModifyTime = NULL; + $loggingEnabled = NULL; + + while ($xmlReader->read()) + { + if ($xmlReader->nodeType == \XMLReader::ELEMENT) + { + switch ($xmlReader->name) { + case 'MaximumMessageSize': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $maximumMessageSize = $xmlReader->value; + } + break; + case 'MessageRetentionPeriod': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $messageRetentionPeriod = $xmlReader->value; + } + break; + case 'TopicName': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $topicName = $xmlReader->value; + } + break; + case 'CreateTime': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $createTime = $xmlReader->value; + } + break; + case 'LastModifyTime': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $lastModifyTime = $xmlReader->value; + } + break; + case 'LoggingEnabled': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $loggingEnabled = $xmlReader->value; + if ($loggingEnabled == "True") + { + $loggingEnabled = True; + } + else + { + $loggingEnabled = False; + } + } + break; + } + } + } + + $attributes = new TopicAttributes( + $maximumMessageSize, + $messageRetentionPeriod, + $topicName, + $createTime, + $lastModifyTime, + $loggingEnabled); + return $attributes; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Model/UpdateSubscriptionAttributes.php b/Lib/Alisms/AliyunMNS/Model/UpdateSubscriptionAttributes.php new file mode 100644 index 0000000..f1b2079 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Model/UpdateSubscriptionAttributes.php @@ -0,0 +1,58 @@ +subscriptionName = $subscriptionName; + + $this->strategy = $strategy; + } + + public function getStrategy() + { + return $this->strategy; + } + + public function setStrategy($strategy) + { + $this->strategy = $strategy; + } + + public function getTopicName() + { + return $this->topicName; + } + + public function setTopicName($topicName) + { + $this->topicName = $topicName; + } + + public function getSubscriptionName() + { + return $this->subscriptionName; + } + + public function writeXML(\XMLWriter $xmlWriter) + { + if ($this->strategy != NULL) + { + $xmlWriter->writeElement(Constants::STRATEGY, $this->strategy); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Model/WebSocketAttributes.php b/Lib/Alisms/AliyunMNS/Model/WebSocketAttributes.php new file mode 100644 index 0000000..c38785b --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Model/WebSocketAttributes.php @@ -0,0 +1,33 @@ +importanceLevel = $importanceLevel; + } + + public function setImportanceLevel($importanceLevel) + { + $this->importanceLevel = $importanceLevel; + } + + public function getImportanceLevel() + { + return $this->importanceLevel; + } + + public function writeXML(\XMLWriter $xmlWriter) + { + $jsonArray = array(Constants::IMPORTANCE_LEVEL => $this->importanceLevel); + $xmlWriter->writeElement(Constants::WEBSOCKET, json_encode($jsonArray)); + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Queue.php b/Lib/Alisms/AliyunMNS/Queue.php new file mode 100644 index 0000000..8d63d51 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Queue.php @@ -0,0 +1,377 @@ +queueName = $queueName; + $this->client = $client; + $this->base64 = $base64; + } + + public function setBase64($base64) + { + $this->base64 = $base64; + } + + public function isBase64() + { + return ($this->base64 == TRUE); + } + + public function getQueueName() + { + return $this->queueName; + } + + /** + * Set the QueueAttributes, detail API sepcs: + * https://docs.aliyun.com/?spm=#/pub/mns/api_reference/api_spec&queue_operation + * + * @param QueueAttributes $attributes: the QueueAttributes to set + * + * @return SetQueueAttributeResponse: the response + * + * @throws QueueNotExistException if queue does not exist + * @throws InvalidArgumentException if any argument value is invalid + * @throws MnsException if any other exception happends + */ + public function setAttribute(QueueAttributes $attributes) + { + $request = new SetQueueAttributeRequest($this->queueName, $attributes); + $response = new SetQueueAttributeResponse(); + return $this->client->sendRequest($request, $response); + } + + public function setAttributeAsync(QueueAttributes $attributes, + AsyncCallback $callback = NULL) + { + $request = new SetQueueAttributeRequest($this->queueName, $attributes); + $response = new SetQueueAttributeResponse(); + return $this->client->sendRequestAsync($request, $response, $callback); + } + + /** + * Get the QueueAttributes, detail API sepcs: + * https://docs.aliyun.com/?spm=#/pub/mns/api_reference/api_spec&queue_operation + * + * @return GetQueueAttributeResponse: containing the attributes + * + * @throws QueueNotExistException if queue does not exist + * @throws MnsException if any other exception happends + */ + public function getAttribute() + { + $request = new GetQueueAttributeRequest($this->queueName); + $response = new GetQueueAttributeResponse(); + return $this->client->sendRequest($request, $response); + } + + public function getAttributeAsync(AsyncCallback $callback = NULL) + { + $request = new GetQueueAttributeRequest($this->queueName); + $response = new GetQueueAttributeResponse(); + return $this->client->sendRequestAsync($request, $response, $callback); + } + + /** + * SendMessage, the messageBody will be automatically encoded in base64 + * If you do not need the message body to be encoded in Base64, + * please specify the $base64 = FALSE in Queue + * + * detail API sepcs: + * https://docs.aliyun.com/?spm=#/pub/mns/api_reference/api_spec&message_operation + * + * @param SendMessageRequest: containing the message body and properties + * + * @return SendMessageResponse: containing the messageId and bodyMD5 + * + * @throws QueueNotExistException if queue does not exist + * @throws InvalidArgumentException if any argument value is invalid + * @throws MalformedXMLException if any error in xml + * @throws MnsException if any other exception happends + */ + public function sendMessage(SendMessageRequest $request) + { + $request->setQueueName($this->queueName); + $request->setBase64($this->base64); + $response = new SendMessageResponse(); + return $this->client->sendRequest($request, $response); + } + + public function sendMessageAsync(SendMessageRequest $request, + AsyncCallback $callback = NULL) + { + $request->setQueueName($this->queueName); + $request->setBase64($this->base64); + $response = new SendMessageResponse(); + return $this->client->sendRequestAsync($request, $response, $callback); + } + + /** + * PeekMessage, the messageBody will be automatically decoded as base64 if the $base64 in Queue is TRUE + * + * detail API sepcs: + * https://docs.aliyun.com/?spm=#/pub/mns/api_reference/api_spec&message_operation + * + * @return PeekMessageResponse: containing the messageBody and properties + * + * @throws QueueNotExistException if queue does not exist + * @throws MessageNotExistException if no message exists in the queue + * @throws MnsException if any other exception happends + */ + public function peekMessage() + { + $request = new PeekMessageRequest($this->queueName); + $response = new PeekMessageResponse($this->base64); + return $this->client->sendRequest($request, $response); + } + + public function peekMessageAsync(AsyncCallback $callback = NULL) + { + $request = new PeekMessageRequest($this->queueName); + $response = new PeekMessageResponse($this->base64); + return $this->client->sendRequestAsync($request, $response, $callback); + } + + /** + * ReceiveMessage, the messageBody will be automatically decoded as base64 if $base64 = TRUE in Queue + * detail API sepcs: + * https://docs.aliyun.com/?spm=#/pub/mns/api_reference/api_spec&message_operation + * + * @param waitSeconds: the long polling waitseconds + * + * @return ReceiveMessageResponse: containing the messageBody and properties + * the response is same as PeekMessageResponse, + * except that the receiptHandle is also returned in receiveMessage + * + * @throws QueueNotExistException if queue does not exist + * @throws MessageNotExistException if no message exists in the queue + * @throws MnsException if any other exception happends + */ + public function receiveMessage($waitSeconds = NULL) + { + $request = new ReceiveMessageRequest($this->queueName, $waitSeconds); + $response = new ReceiveMessageResponse($this->base64); + return $this->client->sendRequest($request, $response); + } + + public function receiveMessageAsync(AsyncCallback $callback = NULL) + { + $request = new ReceiveMessageRequest($this->queueName); + $response = new ReceiveMessageResponse($this->base64); + return $this->client->sendRequestAsync($request, $response, $callback); + } + + /** + * DeleteMessage + * detail API sepcs: + * https://docs.aliyun.com/?spm=#/pub/mns/api_reference/api_spec&message_operation + * + * @param $receiptHandle: the receiptHandle returned from receiveMessage + * + * @return ReceiveMessageResponse + * + * @throws QueueNotExistException if queue does not exist + * @throws InvalidArgumentException if the argument is invalid + * @throws ReceiptHandleErrorException if the $receiptHandle is invalid + * @throws MnsException if any other exception happends + */ + public function deleteMessage($receiptHandle) + { + $request = new DeleteMessageRequest($this->queueName, $receiptHandle); + $response = new DeleteMessageResponse(); + return $this->client->sendRequest($request, $response); + } + + public function deleteMessageAsync($receiptHandle, + AsyncCallback $callback = NULL) + { + $request = new DeleteMessageRequest($this->queueName, $receiptHandle); + $response = new DeleteMessageResponse(); + return $this->client->sendRequestAsync($request, $response, $callback); + } + + /** + * ChangeMessageVisibility, set the nextVisibleTime for the message + * detail API sepcs: + * https://docs.aliyun.com/?spm=#/pub/mns/api_reference/api_spec&message_operation + * + * @param $receiptHandle: the receiptHandle returned from receiveMessage + * + * @return ChangeMessageVisibilityResponse + * + * @throws QueueNotExistException if queue does not exist + * @throws MessageNotExistException if the message does not exist + * @throws InvalidArgumentException if the argument is invalid + * @throws ReceiptHandleErrorException if the $receiptHandle is invalid + * @throws MnsException if any other exception happends + */ + public function changeMessageVisibility($receiptHandle, $visibilityTimeout) + { + $request = new ChangeMessageVisibilityRequest($this->queueName, $receiptHandle, $visibilityTimeout); + $response = new ChangeMessageVisibilityResponse(); + return $this->client->sendRequest($request, $response); + } + + /** + * BatchSendMessage, message body will be automatically encoded in base64 + * If you do not need the message body to be encoded in Base64, + * please specify the $base64 = FALSE in Queue + * + * detail API sepcs: + * https://docs.aliyun.com/?spm=#/pub/mns/api_reference/api_spec&message_operation + * + * @param BatchSendMessageRequest: + * the requests containing an array of SendMessageRequestItems + * + * @return BatchSendMessageResponse + * + * @throws QueueNotExistException if queue does not exist + * @throws MalformedXMLException if any error in the xml + * @throws InvalidArgumentException if the argument is invalid + * @throws BatchSendFailException if some messages are not sent + * @throws MnsException if any other exception happends + */ + public function batchSendMessage(BatchSendMessageRequest $request) + { + $request->setQueueName($this->queueName); + $request->setBase64($this->base64); + $response = new BatchSendMessageResponse(); + return $this->client->sendRequest($request, $response); + } + + public function batchSendMessageAsync(BatchSendMessageRequest $request, + AsyncCallback $callback = NULL) + { + $request->setQueueName($this->queueName); + $request->setBase64($this->base64); + $response = new BatchSendMessageResponse(); + return $this->client->sendRequestAsync($request, $response, $callback); + } + + /** + * BatchReceiveMessage, message body will be automatically decoded as base64 if $base64 = TRUE in Queue + * + * detail API sepcs: + * https://docs.aliyun.com/?spm=#/pub/mns/api_reference/api_spec&message_operation + * + * @param BatchReceiveMessageRequest: + * containing numOfMessages and waitSeconds + * + * @return BatchReceiveMessageResponse: + * the received messages + * + * @throws QueueNotExistException if queue does not exist + * @throws MessageNotExistException if no message exists + * @throws MnsException if any other exception happends + */ + public function batchReceiveMessage(BatchReceiveMessageRequest $request) + { + $request->setQueueName($this->queueName); + $response = new BatchReceiveMessageResponse($this->base64); + return $this->client->sendRequest($request, $response); + } + + public function batchReceiveMessageAsync(BatchReceiveMessageRequest $request, AsyncCallback $callback = NULL) + { + $request->setQueueName($this->queueName); + $response = new BatchReceiveMessageResponse($this->base64); + return $this->client->sendRequestAsync($request, $response, $callback); + } + + /** + * BatchPeekMessage, message body will be automatically decoded as base64 is $base64 = TRUE in Queue + * + * detail API sepcs: + * https://docs.aliyun.com/?spm=#/pub/mns/api_reference/api_spec&message_operation + * + * @param BatchPeekMessageRequest: + * containing numOfMessages and waitSeconds + * + * @return BatchPeekMessageResponse: + * the received messages + * + * @throws QueueNotExistException if queue does not exist + * @throws MessageNotExistException if no message exists + * @throws MnsException if any other exception happends + */ + public function batchPeekMessage($numOfMessages) + { + $request = new BatchPeekMessageRequest($this->queueName, $numOfMessages); + $response = new BatchPeekMessageResponse($this->base64); + return $this->client->sendRequest($request, $response); + } + + public function batchPeekMessageAsync($numOfMessages, AsyncCallback $callback = NULL) + { + $request = new BatchPeekMessageRequest($this->queueName, $numOfMessages); + $response = new BatchPeekMessageResponse($this->base64); + return $this->client->sendRequestAsync($request, $response, $callback); + } + + /** + * BatchDeleteMessage + * detail API sepcs: + * https://docs.aliyun.com/?spm=#/pub/mns/api_reference/api_spec&message_operation + * + * @param $receiptHandles: + * array of $receiptHandle, which is got from receiveMessage + * + * @return BatchDeleteMessageResponse + * + * @throws QueueNotExistException if queue does not exist + * @throws ReceiptHandleErrorException if the receiptHandle is invalid + * @throws InvalidArgumentException if the argument is invalid + * @throws BatchDeleteFailException if any message not deleted + * @throws MnsException if any other exception happends + */ + public function batchDeleteMessage($receiptHandles) + { + $request = new BatchDeleteMessageRequest($this->queueName, $receiptHandles); + $response = new BatchDeleteMessageResponse(); + return $this->client->sendRequest($request, $response); + } + + public function batchDeleteMessageAsync($receiptHandles, AsyncCallback $callback = NULL) + { + $request = new BatchDeleteMessageRequest($this->queueName, $receiptHandles); + $response = new BatchDeleteMessageResponse(); + return $this->client->sendRequestAsync($request, $response, $callback); + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/BaseRequest.php b/Lib/Alisms/AliyunMNS/Requests/BaseRequest.php new file mode 100644 index 0000000..78e98b2 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/BaseRequest.php @@ -0,0 +1,75 @@ +method = $method; + $this->resourcePath = $resourcePath; + } + + abstract public function generateBody(); + abstract public function generateQueryString(); + + public function setBody($body) + { + $this->body = $body; + } + + public function getBody() + { + return $this->body; + } + + public function setQueryString($queryString) + { + $this->queryString = $queryString; + } + + public function getQueryString() + { + return $this->queryString; + } + + public function isHeaderSet($header) + { + return isset($this->headers[$header]); + } + + public function getHeaders() + { + return $this->headers; + } + + public function removeHeader($header) + { + if (isset($this->headers[$header])) + { + unset($this->headers[$header]); + } + } + + public function setHeader($header, $value) + { + $this->headers[$header] = $value; + } + + public function getResourcePath() + { + return $this->resourcePath; + } + + public function getMethod() + { + return $this->method; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/BatchDeleteMessageRequest.php b/Lib/Alisms/AliyunMNS/Requests/BatchDeleteMessageRequest.php new file mode 100644 index 0000000..c79cfa4 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/BatchDeleteMessageRequest.php @@ -0,0 +1,50 @@ +queueName = $queueName; + $this->receiptHandles = $receiptHandles; + } + + public function getQueueName() + { + return $this->queueName; + } + + public function getReceiptHandles() + { + return $this->receiptHandles; + } + + public function generateBody() + { + $xmlWriter = new \XMLWriter; + $xmlWriter->openMemory(); + $xmlWriter->startDocument("1.0", "UTF-8"); + $xmlWriter->startElementNS(NULL, Constants::RECEIPT_HANDLES, Constants::MNS_XML_NAMESPACE); + foreach ($this->receiptHandles as $receiptHandle) + { + $xmlWriter->writeElement(Constants::RECEIPT_HANDLE, $receiptHandle); + } + $xmlWriter->endElement(); + $xmlWriter->endDocument(); + return $xmlWriter->outputMemory(); + } + + public function generateQueryString() + { + return NULL; + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/BatchPeekMessageRequest.php b/Lib/Alisms/AliyunMNS/Requests/BatchPeekMessageRequest.php new file mode 100644 index 0000000..469eb2a --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/BatchPeekMessageRequest.php @@ -0,0 +1,41 @@ +queueName = $queueName; + $this->numOfMessages = $numOfMessages; + } + + public function getQueueName() + { + return $this->queueName; + } + + public function getNumOfMessages() + { + return $this->numOfMessages; + } + + public function generateBody() + { + return NULL; + } + + public function generateQueryString() + { + return http_build_query(array("numOfMessages" => $this->numOfMessages, "peekonly" => "true")); + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/BatchReceiveMessageRequest.php b/Lib/Alisms/AliyunMNS/Requests/BatchReceiveMessageRequest.php new file mode 100644 index 0000000..89090b4 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/BatchReceiveMessageRequest.php @@ -0,0 +1,58 @@ +queueName = NULL; + $this->numOfMessages = $numOfMessages; + $this->waitSeconds = $waitSeconds; + } + + public function setQueueName($queueName) + { + $this->queueName = $queueName; + $this->resourcePath = 'queues/' . $queueName . '/messages'; + } + + public function getQueueName() + { + return $this->queueName; + } + + public function getWaitSeconds() + { + return $this->waitSeconds; + } + + public function getNumOfMessages() + { + return $this->numOfMessages; + } + + public function generateBody() + { + return NULL; + } + + public function generateQueryString() + { + $params = array("numOfMessages" => $this->numOfMessages); + if ($this->waitSeconds != NULL) + { + $params["waitseconds"] = $this->waitSeconds; + } + return http_build_query($params); + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/BatchSendMessageRequest.php b/Lib/Alisms/AliyunMNS/Requests/BatchSendMessageRequest.php new file mode 100644 index 0000000..9074a36 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/BatchSendMessageRequest.php @@ -0,0 +1,76 @@ +queueName = NULL; + $this->sendMessageRequestItems = $sendMessageRequestItems; + $this->base64 = $base64; + } + + public function setBase64($base64) + { + $this->base64 = $base64; + } + + public function isBase64() + { + return ($this->base64 == TRUE); + } + + public function setQueueName($queueName) + { + $this->queueName = $queueName; + $this->resourcePath = 'queues/' . $queueName . '/messages'; + } + + public function getQueueName() + { + return $this->queueName; + } + + public function getSendMessageRequestItems() + { + return $this->sendMessageRequestItems; + } + + public function addSendMessageRequestItem(SendMessageRequestItem $item) + { + $this->sendMessageRequestItems[] = $item; + } + + public function generateBody() + { + $xmlWriter = new \XMLWriter; + $xmlWriter->openMemory(); + $xmlWriter->startDocument("1.0", "UTF-8"); + $xmlWriter->startElementNS(NULL, "Messages", Constants::MNS_XML_NAMESPACE); + foreach ($this->sendMessageRequestItems as $item) + { + $item->writeXML($xmlWriter, $this->base64); + } + $xmlWriter->endElement(); + $xmlWriter->endDocument(); + return $xmlWriter->outputMemory(); + } + + public function generateQueryString() + { + return NULL; + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/ChangeMessageVisibilityRequest.php b/Lib/Alisms/AliyunMNS/Requests/ChangeMessageVisibilityRequest.php new file mode 100644 index 0000000..2c9be59 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/ChangeMessageVisibilityRequest.php @@ -0,0 +1,47 @@ +queueName = $queueName; + $this->receiptHandle = $receiptHandle; + $this->visibilityTimeout = $visibilityTimeout; + } + + public function getQueueName() + { + return $this->queueName; + } + + public function getReceiptHandle() + { + return $this->receiptHandle; + } + + public function getVisibilityTimeout() + { + return $this->visibilityTimeout; + } + + public function generateBody() + { + return NULL; + } + + public function generateQueryString() + { + return http_build_query(array("receiptHandle" => $this->receiptHandle, "visibilityTimeout" => $this->visibilityTimeout)); + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/CreateQueueRequest.php b/Lib/Alisms/AliyunMNS/Requests/CreateQueueRequest.php new file mode 100644 index 0000000..4b16f38 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/CreateQueueRequest.php @@ -0,0 +1,53 @@ +queueName = $queueName; + $this->attributes = $attributes; + } + + public function getQueueName() + { + return $this->queueName; + } + + public function getQueueAttributes() + { + return $this->attributes; + } + + public function generateBody() + { + $xmlWriter = new \XMLWriter; + $xmlWriter->openMemory(); + $xmlWriter->startDocument("1.0", "UTF-8"); + $xmlWriter->startElementNS(NULL, "Queue", Constants::MNS_XML_NAMESPACE); + $this->attributes->writeXML($xmlWriter); + $xmlWriter->endElement(); + $xmlWriter->endDocument(); + return $xmlWriter->outputMemory(); + } + + public function generateQueryString() + { + return NULL; + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/CreateTopicRequest.php b/Lib/Alisms/AliyunMNS/Requests/CreateTopicRequest.php new file mode 100644 index 0000000..65fc94a --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/CreateTopicRequest.php @@ -0,0 +1,53 @@ +topicName = $topicName; + $this->attributes = $attributes; + } + + public function getTopicName() + { + return $this->topicName; + } + + public function getTopicAttributes() + { + return $this->attributes; + } + + public function generateBody() + { + $xmlWriter = new \XMLWriter; + $xmlWriter->openMemory(); + $xmlWriter->startDocument("1.0", "UTF-8"); + $xmlWriter->startElementNS(NULL, "Topic", Constants::MNS_XML_NAMESPACE); + $this->attributes->writeXML($xmlWriter); + $xmlWriter->endElement(); + $xmlWriter->endDocument(); + return $xmlWriter->outputMemory(); + } + + public function generateQueryString() + { + return NULL; + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/DeleteMessageRequest.php b/Lib/Alisms/AliyunMNS/Requests/DeleteMessageRequest.php new file mode 100644 index 0000000..ad52aad --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/DeleteMessageRequest.php @@ -0,0 +1,40 @@ +queueName = $queueName; + $this->receiptHandle = $receiptHandle; + } + + public function getQueueName() + { + return $this->queueName; + } + + public function getReceiptHandle() + { + return $this->receiptHandle; + } + + public function generateBody() + { + return NULL; + } + + public function generateQueryString() + { + return http_build_query(array("ReceiptHandle" => $this->receiptHandle)); + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/DeleteQueueRequest.php b/Lib/Alisms/AliyunMNS/Requests/DeleteQueueRequest.php new file mode 100644 index 0000000..776ba97 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/DeleteQueueRequest.php @@ -0,0 +1,33 @@ +queueName = $queueName; + } + + public function getQueueName() + { + return $this->queueName; + } + + public function generateBody() + { + return NULL; + } + + public function generateQueryString() + { + return NULL; + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/DeleteTopicRequest.php b/Lib/Alisms/AliyunMNS/Requests/DeleteTopicRequest.php new file mode 100644 index 0000000..2a08a35 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/DeleteTopicRequest.php @@ -0,0 +1,33 @@ +topicName = $topicName; + } + + public function getTopicName() + { + return $this->topicName; + } + + public function generateBody() + { + return NULL; + } + + public function generateQueryString() + { + return NULL; + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/GetAccountAttributesRequest.php b/Lib/Alisms/AliyunMNS/Requests/GetAccountAttributesRequest.php new file mode 100644 index 0000000..3b15aa7 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/GetAccountAttributesRequest.php @@ -0,0 +1,23 @@ + diff --git a/Lib/Alisms/AliyunMNS/Requests/GetQueueAttributeRequest.php b/Lib/Alisms/AliyunMNS/Requests/GetQueueAttributeRequest.php new file mode 100644 index 0000000..e10231a --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/GetQueueAttributeRequest.php @@ -0,0 +1,32 @@ +queueName = $queueName; + } + + public function getQueueName() + { + return $this->queueName; + } + + public function generateBody() + { + return NULL; + } + + public function generateQueryString() + { + return NULL; + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/GetSubscriptionAttributeRequest.php b/Lib/Alisms/AliyunMNS/Requests/GetSubscriptionAttributeRequest.php new file mode 100644 index 0000000..a417b12 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/GetSubscriptionAttributeRequest.php @@ -0,0 +1,39 @@ +topicName = $topicName; + $this->subscriptionName = $subscriptionName; + } + + public function getTopicName() + { + return $this->topicName; + } + + public function getSubscriptionName() + { + return $this->subscriptionName; + } + + public function generateBody() + { + return NULL; + } + + public function generateQueryString() + { + return NULL; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/GetTopicAttributeRequest.php b/Lib/Alisms/AliyunMNS/Requests/GetTopicAttributeRequest.php new file mode 100644 index 0000000..5880a98 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/GetTopicAttributeRequest.php @@ -0,0 +1,32 @@ +topicName = $topicName; + } + + public function getTopicName() + { + return $this->topicName; + } + + public function generateBody() + { + return NULL; + } + + public function generateQueryString() + { + return NULL; + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/ListQueueRequest.php b/Lib/Alisms/AliyunMNS/Requests/ListQueueRequest.php new file mode 100644 index 0000000..8b58774 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/ListQueueRequest.php @@ -0,0 +1,86 @@ +setRetNum($retNum); + $this->setPrefix($prefix); + $this->setMarker($marker); + } + + public function getRetNum() + { + return $this->retNum; + } + + public function setRetNum($retNum) + { + $this->retNum = $retNum; + if ($retNum != NULL) + { + $this->setHeader("x-mns-ret-number", $retNum); + } + else + { + $this->removeHeader("x-mns-ret-number"); + } + } + + public function getPrefix() + { + return $this->prefix; + } + + public function setPrefix($prefix) + { + $this->prefis = $prefix; + if ($prefix != NULL) + { + $this->setHeader("x-mns-prefix", $prefix); + } + else + { + $this->removeHeader("x-mns-prefix"); + } + } + + public function getMarker() + { + return $this->marker; + } + + public function setMarker($marker) + { + $this->marker = $marker; + if ($marker != NULL) + { + $this->setHeader("x-mns-marker", $marker); + } + else + { + $this->removeHeader("x-mns-marker"); + } + } + + public function generateBody() + { + return NULL; + } + + public function generateQueryString() + { + return NULL; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/ListSubscriptionRequest.php b/Lib/Alisms/AliyunMNS/Requests/ListSubscriptionRequest.php new file mode 100644 index 0000000..bb4b331 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/ListSubscriptionRequest.php @@ -0,0 +1,97 @@ +topicName = $topicName; + $this->setRetNum($retNum); + $this->setPrefix($prefix); + $this->setMarker($marker); + } + + public function getTopicName() + { + return $this->topicName; + } + + public function getRetNum() + { + return $this->retNum; + } + + public function setRetNum($retNum) + { + $this->retNum = $retNum; + if ($retNum != NULL) + { + $this->setHeader("x-mns-ret-number", $retNum); + } + else + { + $this->removeHeader("x-mns-ret-number"); + } + } + + public function getPrefix() + { + return $this->prefix; + } + + public function setPrefix($prefix) + { + $this->prefis = $prefix; + if ($prefix != NULL) + { + $this->setHeader("x-mns-prefix", $prefix); + } + else + { + $this->removeHeader("x-mns-prefix"); + } + } + + public function getMarker() + { + return $this->marker; + } + + public function setMarker($marker) + { + $this->marker = $marker; + if ($marker != NULL) + { + $this->setHeader("x-mns-marker", $marker); + } + else + { + $this->removeHeader("x-mns-marker"); + } + } + + public function generateBody() + { + return NULL; + } + + public function generateQueryString() + { + return NULL; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/ListTopicRequest.php b/Lib/Alisms/AliyunMNS/Requests/ListTopicRequest.php new file mode 100644 index 0000000..1d8f97b --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/ListTopicRequest.php @@ -0,0 +1,86 @@ +setRetNum($retNum); + $this->setPrefix($prefix); + $this->setMarker($marker); + } + + public function getRetNum() + { + return $this->retNum; + } + + public function setRetNum($retNum) + { + $this->retNum = $retNum; + if ($retNum != NULL) + { + $this->setHeader("x-mns-ret-number", $retNum); + } + else + { + $this->removeHeader("x-mns-ret-number"); + } + } + + public function getPrefix() + { + return $this->prefix; + } + + public function setPrefix($prefix) + { + $this->prefis = $prefix; + if ($prefix != NULL) + { + $this->setHeader("x-mns-prefix", $prefix); + } + else + { + $this->removeHeader("x-mns-prefix"); + } + } + + public function getMarker() + { + return $this->marker; + } + + public function setMarker($marker) + { + $this->marker = $marker; + if ($marker != NULL) + { + $this->setHeader("x-mns-marker", $marker); + } + else + { + $this->removeHeader("x-mns-marker"); + } + } + + public function generateBody() + { + return NULL; + } + + public function generateQueryString() + { + return NULL; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/PeekMessageRequest.php b/Lib/Alisms/AliyunMNS/Requests/PeekMessageRequest.php new file mode 100644 index 0000000..fbe02f0 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/PeekMessageRequest.php @@ -0,0 +1,33 @@ +queueName = $queueName; + } + + public function getQueueName() + { + return $this->queueName; + } + + public function generateBody() + { + return NULL; + } + + public function generateQueryString() + { + return NULL; + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/PublishMessageRequest.php b/Lib/Alisms/AliyunMNS/Requests/PublishMessageRequest.php new file mode 100644 index 0000000..f66bfa0 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/PublishMessageRequest.php @@ -0,0 +1,51 @@ +topicName = NULL; + $this->messageBody = $messageBody; + $this->messageAttributes = $messageAttributes; + } + + public function setTopicName($topicName) + { + $this->topicName = $topicName; + $this->resourcePath = 'topics/' . $topicName . '/messages'; + } + + public function getTopicName() + { + return $this->topicName; + } + + public function generateBody() + { + $xmlWriter = new \XMLWriter; + $xmlWriter->openMemory(); + $xmlWriter->startDocument("1.0", "UTF-8"); + $xmlWriter->startElementNS(NULL, "Message", Constants::MNS_XML_NAMESPACE); + $this->writeMessagePropertiesForPublishXML($xmlWriter); + $xmlWriter->endElement(); + $xmlWriter->endDocument(); + return $xmlWriter->outputMemory(); + } + + public function generateQueryString() + { + return NULL; + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/ReceiveMessageRequest.php b/Lib/Alisms/AliyunMNS/Requests/ReceiveMessageRequest.php new file mode 100644 index 0000000..dd853d5 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/ReceiveMessageRequest.php @@ -0,0 +1,43 @@ +queueName = $queueName; + $this->waitSeconds = $waitSeconds; + } + + public function getQueueName() + { + return $this->queueName; + } + + public function getWaitSeconds() + { + return $this->waitSeconds; + } + + public function generateBody() + { + return NULL; + } + + public function generateQueryString() + { + if ($this->waitSeconds != NULL) + { + return http_build_query(array("waitseconds" => $this->waitSeconds)); + } + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/SendMessageRequest.php b/Lib/Alisms/AliyunMNS/Requests/SendMessageRequest.php new file mode 100644 index 0000000..113fba0 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/SendMessageRequest.php @@ -0,0 +1,67 @@ +queueName = NULL; + $this->messageBody = $messageBody; + $this->delaySeconds = $delaySeconds; + $this->priority = $priority; + $this->base64 = $base64; + } + + public function setBase64($base64) + { + $this->base64 = $base64; + } + + public function isBase64() + { + return ($this->base64 == TRUE); + } + + public function setQueueName($queueName) + { + $this->queueName = $queueName; + $this->resourcePath = 'queues/' . $queueName . '/messages'; + } + + public function getQueueName() + { + return $this->queueName; + } + + public function generateBody() + { + $xmlWriter = new \XMLWriter; + $xmlWriter->openMemory(); + $xmlWriter->startDocument("1.0", "UTF-8"); + $xmlWriter->startElementNS(NULL, "Message", Constants::MNS_XML_NAMESPACE); + $this->writeMessagePropertiesForSendXML($xmlWriter, $this->base64); + $xmlWriter->endElement(); + $xmlWriter->endDocument(); + return $xmlWriter->outputMemory(); + } + + public function generateQueryString() + { + return NULL; + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/SetAccountAttributesRequest.php b/Lib/Alisms/AliyunMNS/Requests/SetAccountAttributesRequest.php new file mode 100644 index 0000000..89ca743 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/SetAccountAttributesRequest.php @@ -0,0 +1,46 @@ +attributes = $attributes; + } + + public function getAccountAttributes() + { + return $this->attributes; + } + + public function generateBody() + { + $xmlWriter = new \XMLWriter; + $xmlWriter->openMemory(); + $xmlWriter->startDocument("1.0", "UTF-8"); + $xmlWriter->startElementNS(NULL, "Account", Constants::MNS_XML_NAMESPACE); + $this->attributes->writeXML($xmlWriter); + $xmlWriter->endElement(); + $xmlWriter->endDocument(); + return $xmlWriter->outputMemory(); + } + + public function generateQueryString() + { + return NULL; + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/SetQueueAttributeRequest.php b/Lib/Alisms/AliyunMNS/Requests/SetQueueAttributeRequest.php new file mode 100644 index 0000000..f6b4ff0 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/SetQueueAttributeRequest.php @@ -0,0 +1,53 @@ +queueName = $queueName; + $this->attributes = $attributes; + } + + public function getQueueName() + { + return $this->queueName; + } + + public function getQueueAttributes() + { + return $this->attributes; + } + + public function generateBody() + { + $xmlWriter = new \XMLWriter; + $xmlWriter->openMemory(); + $xmlWriter->startDocument("1.0", "UTF-8"); + $xmlWriter->startElementNS(NULL, "Queue", Constants::MNS_XML_NAMESPACE); + $this->attributes->writeXML($xmlWriter); + $xmlWriter->endElement(); + $xmlWriter->endDocument(); + return $xmlWriter->outputMemory(); + } + + public function generateQueryString() + { + return NULL; + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/SetSubscriptionAttributeRequest.php b/Lib/Alisms/AliyunMNS/Requests/SetSubscriptionAttributeRequest.php new file mode 100644 index 0000000..c9489c1 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/SetSubscriptionAttributeRequest.php @@ -0,0 +1,46 @@ +getTopicName() . '/subscriptions/' . $attributes->getSubscriptionName() . '?metaoverride=true'); + + if ($attributes == NULL) + { + $attributes = new UpdateSubscriptionAttributes(); + } + + $this->attributes = $attributes; + } + + public function getSubscriptionAttributes() + { + return $this->attributes; + } + + public function generateBody() + { + $xmlWriter = new \XMLWriter; + $xmlWriter->openMemory(); + $xmlWriter->startDocument("1.0", "UTF-8"); + $xmlWriter->startElementNS(NULL, "Subscription", Constants::MNS_XML_NAMESPACE); + $this->attributes->writeXML($xmlWriter); + $xmlWriter->endElement(); + $xmlWriter->endDocument(); + return $xmlWriter->outputMemory(); + } + + public function generateQueryString() + { + return NULL; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/SetTopicAttributeRequest.php b/Lib/Alisms/AliyunMNS/Requests/SetTopicAttributeRequest.php new file mode 100644 index 0000000..7161a4a --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/SetTopicAttributeRequest.php @@ -0,0 +1,53 @@ +topicName = $topicName; + $this->attributes = $attributes; + } + + public function getTopicName() + { + return $this->topicName; + } + + public function getTopicAttributes() + { + return $this->attributes; + } + + public function generateBody() + { + $xmlWriter = new \XMLWriter; + $xmlWriter->openMemory(); + $xmlWriter->startDocument("1.0", "UTF-8"); + $xmlWriter->startElementNS(NULL, "Topic", Constants::MNS_XML_NAMESPACE); + $this->attributes->writeXML($xmlWriter); + $xmlWriter->endElement(); + $xmlWriter->endDocument(); + return $xmlWriter->outputMemory(); + } + + public function generateQueryString() + { + return NULL; + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/SubscribeRequest.php b/Lib/Alisms/AliyunMNS/Requests/SubscribeRequest.php new file mode 100644 index 0000000..8dcd2f9 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/SubscribeRequest.php @@ -0,0 +1,42 @@ +getTopicName() . '/subscriptions/' . $attributes->getSubscriptionName()); + + $this->attributes = $attributes; + } + + public function getSubscriptionAttributes() + { + return $this->attributes; + } + + public function generateBody() + { + $xmlWriter = new \XMLWriter; + $xmlWriter->openMemory(); + $xmlWriter->startDocument("1.0", "UTF-8"); + $xmlWriter->startElementNS(NULL, "Subscription", Constants::MNS_XML_NAMESPACE); + $this->attributes->writeXML($xmlWriter); + $xmlWriter->endElement(); + $xmlWriter->endDocument(); + return $xmlWriter->outputMemory(); + } + + public function generateQueryString() + { + return NULL; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Requests/UnsubscribeRequest.php b/Lib/Alisms/AliyunMNS/Requests/UnsubscribeRequest.php new file mode 100644 index 0000000..94a10e4 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Requests/UnsubscribeRequest.php @@ -0,0 +1,39 @@ +topicName = $topicName; + $this->subscriptionName = $subscriptionName; + } + + public function getTopicName() + { + return $this->topicName; + } + + public function getSubscriptionName() + { + return $this->subscriptionName; + } + + public function generateBody() + { + return NULL; + } + + public function generateQueryString() + { + return NULL; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/BaseResponse.php b/Lib/Alisms/AliyunMNS/Responses/BaseResponse.php new file mode 100644 index 0000000..b342f0a --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/BaseResponse.php @@ -0,0 +1,39 @@ +succeed; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + protected function loadXmlContent($content) + { + $xmlReader = new \XMLReader(); + $isXml = $xmlReader->XML($content); + if ($isXml === FALSE) { + throw new MnsException($this->statusCode, $content); + } + try { + while ($xmlReader->read()) {} + } catch (\Exception $e) { + throw new MnsException($this->statusCode, $content); + } + $xmlReader->XML($content); + return $xmlReader; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/BatchDeleteMessageResponse.php b/Lib/Alisms/AliyunMNS/Responses/BatchDeleteMessageResponse.php new file mode 100644 index 0000000..90266a0 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/BatchDeleteMessageResponse.php @@ -0,0 +1,95 @@ +statusCode = $statusCode; + if ($statusCode == 204) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + + try { + while ($xmlReader->read()) + { + if ($xmlReader->nodeType == \XMLReader::ELEMENT) { + switch ($xmlReader->name) { + case Constants::ERROR: + $this->parseNormalErrorResponse($xmlReader); + break; + default: // case Constants::Messages + $this->parseBatchDeleteErrorResponse($xmlReader); + break; + } + } + } + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } + + private function parseBatchDeleteErrorResponse($xmlReader) + { + $ex = new BatchDeleteFailException($this->statusCode, "BatchDeleteMessage Failed For Some ReceiptHandles"); + while ($xmlReader->read()) + { + if ($xmlReader->nodeType == \XMLReader::ELEMENT && $xmlReader->name == Constants::ERROR) { + $ex->addDeleteMessageErrorItem( DeleteMessageErrorItem::fromXML($xmlReader)); + } + } + throw $ex; + } + + private function parseNormalErrorResponse($xmlReader) + { + $result = XMLParser::parseNormalError($xmlReader); + + if ($result['Code'] == Constants::INVALID_ARGUMENT) + { + throw new InvalidArgumentException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::QUEUE_NOT_EXIST) + { + throw new QueueNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::RECEIPT_HANDLE_ERROR) + { + throw new ReceiptHandleErrorException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/BatchPeekMessageResponse.php b/Lib/Alisms/AliyunMNS/Responses/BatchPeekMessageResponse.php new file mode 100644 index 0000000..46108f2 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/BatchPeekMessageResponse.php @@ -0,0 +1,97 @@ +messages = array(); + $this->base64 = $base64; + } + + public function setBase64($base64) + { + $this->base64 = $base64; + } + + public function isBase64() + { + return ($this->base64 == TRUE); + } + + public function getMessages() + { + return $this->messages; + } + + public function parseResponse($statusCode, $content) + { + $this->statusCode = $statusCode; + if ($statusCode == 200) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + + $xmlReader = $this->loadXmlContent($content); + + try { + while ($xmlReader->read()) + { + if ($xmlReader->nodeType == \XMLReader::ELEMENT + && $xmlReader->name == 'Message') + { + $this->messages[] = Message::fromXML($xmlReader, $this->base64); + } + } + } catch (\Exception $e) { + throw new MnsException($statusCode, $e->getMessage(), $e); + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + + try { + $result = XMLParser::parseNormalError($xmlReader); + if ($result['Code'] == Constants::QUEUE_NOT_EXIST) + { + throw new QueueNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::MESSAGE_NOT_EXIST) + { + throw new MessageNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/BatchReceiveMessageResponse.php b/Lib/Alisms/AliyunMNS/Responses/BatchReceiveMessageResponse.php new file mode 100644 index 0000000..6978311 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/BatchReceiveMessageResponse.php @@ -0,0 +1,98 @@ +messages = array(); + $this->base64 = $base64; + } + + public function setBase64($base64) + { + $this->base64 = $base64; + } + + public function isBase64() + { + return ($this->base64 == TRUE); + } + + public function getMessages() + { + return $this->messages; + } + + public function parseResponse($statusCode, $content) + { + $this->statusCode = $statusCode; + if ($statusCode == 200) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + + $xmlReader = $this->loadXmlContent($content); + + try { + while ($xmlReader->read()) + { + if ($xmlReader->nodeType == \XMLReader::ELEMENT + && $xmlReader->name == 'Message') + { + $this->messages[] = Message::fromXML($xmlReader, $this->base64); + } + } + } catch (\Exception $e) { + throw new MnsException($statusCode, $e->getMessage(), $e); + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + + try { + $result = XMLParser::parseNormalError($xmlReader); + if ($result['Code'] == Constants::QUEUE_NOT_EXIST) + { + throw new QueueNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::MESSAGE_NOT_EXIST) + { + throw new MessageNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/BatchSendMessageResponse.php b/Lib/Alisms/AliyunMNS/Responses/BatchSendMessageResponse.php new file mode 100644 index 0000000..30ed7a7 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/BatchSendMessageResponse.php @@ -0,0 +1,117 @@ +sendMessageResponseItems = array(); + } + + public function getSendMessageResponseItems() + { + return $this->sendMessageResponseItems; + } + + public function parseResponse($statusCode, $content) + { + $this->statusCode = $statusCode; + if ($statusCode == 201) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + + $xmlReader = $this->loadXmlContent($content); + + try { + while ($xmlReader->read()) + { + if ($xmlReader->nodeType == \XMLReader::ELEMENT && $xmlReader->name == 'Message') { + $this->sendMessageResponseItems[] = SendMessageResponseItem::fromXML($xmlReader); + } + } + } catch (\Exception $e) { + throw new MnsException($statusCode, $e->getMessage(), $e); + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + + try { + while ($xmlReader->read()) + { + if ($xmlReader->nodeType == \XMLReader::ELEMENT) { + switch ($xmlReader->name) { + case Constants::ERROR: + $this->parseNormalErrorResponse($xmlReader); + break; + default: // case Constants::Messages + $this->parseBatchSendErrorResponse($xmlReader); + break; + } + } + } + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } + + private function parseBatchSendErrorResponse($xmlReader) + { + $ex = new BatchSendFailException($this->statusCode, "BatchSendMessage Failed For Some Messages"); + while ($xmlReader->read()) + { + if ($xmlReader->nodeType == \XMLReader::ELEMENT && $xmlReader->name == 'Message') { + $ex->addSendMessageResponseItem( SendMessageResponseItem::fromXML($xmlReader)); + } + } + throw $ex; + } + + private function parseNormalErrorResponse($xmlReader) + { + $result = XMLParser::parseNormalError($xmlReader); + if ($result['Code'] == Constants::QUEUE_NOT_EXIST) + { + throw new QueueNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::INVALID_ARGUMENT) + { + throw new InvalidArgumentException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::MALFORMED_XML) + { + throw new MalformedXMLException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/ChangeMessageVisibilityResponse.php b/Lib/Alisms/AliyunMNS/Responses/ChangeMessageVisibilityResponse.php new file mode 100644 index 0000000..5301c21 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/ChangeMessageVisibilityResponse.php @@ -0,0 +1,96 @@ +receiptHandle; + } + + public function getNextVisibleTime() + { + return $this->nextVisibleTime; + } + + public function parseResponse($statusCode, $content) + { + $this->statusCode = $statusCode; + if ($statusCode == 200) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + + $xmlReader = $this->loadXmlContent($content); + + try { + $message = Message::fromXML($xmlReader, TRUE); + $this->receiptHandle = $message->getReceiptHandle(); + $this->nextVisibleTime = $message->getNextVisibleTime(); + } catch (\Exception $e) { + throw new MnsException($statusCode, $e->getMessage(), $e); + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + + try { + $result = XMLParser::parseNormalError($xmlReader); + + if ($result['Code'] == Constants::INVALID_ARGUMENT) + { + throw new InvalidArgumentException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::QUEUE_NOT_EXIST) + { + throw new QueueNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::MESSAGE_NOT_EXIST) + { + throw new MessageNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::RECEIPT_HANDLE_ERROR) + { + throw new ReceiptHandleErrorException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/CreateQueueResponse.php b/Lib/Alisms/AliyunMNS/Responses/CreateQueueResponse.php new file mode 100644 index 0000000..2a7c1f1 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/CreateQueueResponse.php @@ -0,0 +1,66 @@ +queueName = $queueName; + } + + public function parseResponse($statusCode, $content) + { + $this->statusCode = $statusCode; + if ($statusCode == 201 || $statusCode == 204) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + + try { + $result = XMLParser::parseNormalError($xmlReader); + + if ($result['Code'] == Constants::INVALID_ARGUMENT) + { + throw new InvalidArgumentException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::QUEUE_ALREADY_EXIST) + { + throw new QueueAlreadyExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } + + public function getQueueName() + { + return $this->queueName; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/CreateTopicResponse.php b/Lib/Alisms/AliyunMNS/Responses/CreateTopicResponse.php new file mode 100644 index 0000000..aa75b47 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/CreateTopicResponse.php @@ -0,0 +1,66 @@ +topicName = $topicName; + } + + public function parseResponse($statusCode, $content) + { + $this->statusCode = $statusCode; + if ($statusCode == 201 || $statusCode == 204) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + + try { + $result = XMLParser::parseNormalError($xmlReader); + + if ($result['Code'] == Constants::INVALID_ARGUMENT) + { + throw new InvalidArgumentException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::TOPIC_ALREADY_EXIST) + { + throw new TopicAlreadyExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } + + public function getTopicName() + { + return $this->topicName; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/DeleteMessageResponse.php b/Lib/Alisms/AliyunMNS/Responses/DeleteMessageResponse.php new file mode 100644 index 0000000..ac3c10b --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/DeleteMessageResponse.php @@ -0,0 +1,64 @@ +statusCode = $statusCode; + if ($statusCode == 204) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + + try { + $result = XMLParser::parseNormalError($xmlReader); + + if ($result['Code'] == Constants::INVALID_ARGUMENT) + { + throw new InvalidArgumentException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::QUEUE_NOT_EXIST) + { + throw new QueueNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::RECEIPT_HANDLE_ERROR) + { + throw new ReceiptHandleErrorException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/DeleteQueueResponse.php b/Lib/Alisms/AliyunMNS/Responses/DeleteQueueResponse.php new file mode 100644 index 0000000..751b2b5 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/DeleteQueueResponse.php @@ -0,0 +1,46 @@ +statusCode = $statusCode; + if ($statusCode == 204) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + + try { + $result = XMLParser::parseNormalError($xmlReader); + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/DeleteTopicResponse.php b/Lib/Alisms/AliyunMNS/Responses/DeleteTopicResponse.php new file mode 100644 index 0000000..dcb4368 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/DeleteTopicResponse.php @@ -0,0 +1,46 @@ +statusCode = $statusCode; + if ($statusCode == 204) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + + try { + $result = XMLParser::parseNormalError($xmlReader); + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/GetAccountAttributesResponse.php b/Lib/Alisms/AliyunMNS/Responses/GetAccountAttributesResponse.php new file mode 100644 index 0000000..0aba419 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/GetAccountAttributesResponse.php @@ -0,0 +1,68 @@ +attributes = NULL; + } + + public function getAccountAttributes() + { + return $this->attributes; + } + + public function parseResponse($statusCode, $content) + { + $this->statusCode = $statusCode; + if ($statusCode == 200) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + + $xmlReader = $this->loadXmlContent($content); + + try { + $this->attributes = AccountAttributes::fromXML($xmlReader); + } catch (\Exception $e) { + throw new MnsException($statusCode, $e->getMessage(), $e); + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + + try { + $result = XMLParser::parseNormalError($xmlReader); + + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/GetQueueAttributeResponse.php b/Lib/Alisms/AliyunMNS/Responses/GetQueueAttributeResponse.php new file mode 100644 index 0000000..82de2bd --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/GetQueueAttributeResponse.php @@ -0,0 +1,73 @@ +attributes = NULL; + } + + public function getQueueAttributes() + { + return $this->attributes; + } + + public function parseResponse($statusCode, $content) + { + $this->statusCode = $statusCode; + if ($statusCode == 200) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + + $xmlReader = $this->loadXmlContent($content); + + try { + $this->attributes = QueueAttributes::fromXML($xmlReader); + } catch (\Exception $e) { + throw new MnsException($statusCode, $e->getMessage(), $e); + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + + try { + $result = XMLParser::parseNormalError($xmlReader); + if ($result['Code'] == Constants::QUEUE_NOT_EXIST) + { + throw new QueueNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/GetSubscriptionAttributeResponse.php b/Lib/Alisms/AliyunMNS/Responses/GetSubscriptionAttributeResponse.php new file mode 100644 index 0000000..e0eea96 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/GetSubscriptionAttributeResponse.php @@ -0,0 +1,88 @@ +attributes = NULL; + } + + public function getSubscriptionAttributes() + { + return $this->attributes; + } + + public function parseResponse($statusCode, $content) + { + $this->statusCode = $statusCode; + if ($statusCode == 200) + { + $this->succeed = TRUE; + } + else + { + $this->parseErrorResponse($statusCode, $content); + } + + $xmlReader = $this->loadXmlContent($content); + + try + { + $this->attributes = SubscriptionAttributes::fromXML($xmlReader); + } + catch (\Exception $e) + { + throw new MnsException($statusCode, $e->getMessage(), $e); + } + catch (\Throwable $t) + { + throw new MnsException($statusCode, $t->getMessage()); + } + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + + try + { + $result = XMLParser::parseNormalError($xmlReader); + if ($result['Code'] == Constants::SUBSCRIPTION_NOT_EXIST) + { + throw new SubscriptionNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + catch (\Exception $e) + { + if ($exception != NULL) + { + throw $exception; + } + elseif ($e instanceof MnsException) + { + throw $e; + } + else + { + throw new MnsException($statusCode, $e->getMessage()); + } + } + catch (\Throwable $t) + { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/GetTopicAttributeResponse.php b/Lib/Alisms/AliyunMNS/Responses/GetTopicAttributeResponse.php new file mode 100644 index 0000000..38b1aee --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/GetTopicAttributeResponse.php @@ -0,0 +1,88 @@ +attributes = NULL; + } + + public function getTopicAttributes() + { + return $this->attributes; + } + + public function parseResponse($statusCode, $content) + { + $this->statusCode = $statusCode; + if ($statusCode == 200) + { + $this->succeed = TRUE; + } + else + { + $this->parseErrorResponse($statusCode, $content); + } + + $xmlReader = $this->loadXmlContent($content); + + try { + $this->attributes = TopicAttributes::fromXML($xmlReader); + } + catch (\Exception $e) + { + throw new MnsException($statusCode, $e->getMessage(), $e); + } + catch (\Throwable $t) + { + throw new MnsException($statusCode, $t->getMessage()); + } + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + + try + { + $result = XMLParser::parseNormalError($xmlReader); + if ($result['Code'] == Constants::TOPIC_NOT_EXIST) + { + throw new TopicNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + catch (\Exception $e) + { + if ($exception != NULL) + { + throw $exception; + } + elseif ($e instanceof MnsException) + { + throw $e; + } + else + { + throw new MnsException($statusCode, $e->getMessage()); + } + } + catch (\Throwable $t) + { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/ListQueueResponse.php b/Lib/Alisms/AliyunMNS/Responses/ListQueueResponse.php new file mode 100644 index 0000000..79f3cfd --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/ListQueueResponse.php @@ -0,0 +1,109 @@ +queueNames = array(); + $this->nextMarker = NULL; + } + + public function isFinished() + { + return $this->nextMarker == NULL; + } + + public function getQueueNames() + { + return $this->queueNames; + } + + public function getNextMarker() + { + return $this->nextMarker; + } + + public function parseResponse($statusCode, $content) + { + $this->statusCode = $statusCode; + if ($statusCode != 200) { + $this->parseErrorResponse($statusCode, $content); + return; + } + + $this->succeed = TRUE; + $xmlReader = $this->loadXmlContent($content); + + try { + while ($xmlReader->read()) + { + if ($xmlReader->nodeType == \XMLReader::ELEMENT) + { + switch ($xmlReader->name) { + case 'QueueURL': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $queueName = $this->getQueueNameFromQueueURL($xmlReader->value); + $this->queueNames[] = $queueName; + } + break; + case 'NextMarker': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $this->nextMarker = $xmlReader->value; + } + break; + } + } + } + } catch (\Exception $e) { + throw new MnsException($statusCode, $e->getMessage(), $e); + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } + + private function getQueueNameFromQueueURL($queueURL) + { + $pieces = explode("/", $queueURL); + if (count($pieces) == 5) + { + return $pieces[4]; + } + return ""; + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + + try { + $result = XMLParser::parseNormalError($xmlReader); + + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/ListSubscriptionResponse.php b/Lib/Alisms/AliyunMNS/Responses/ListSubscriptionResponse.php new file mode 100644 index 0000000..a6e0fb2 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/ListSubscriptionResponse.php @@ -0,0 +1,121 @@ +SubscriptionNames = array(); + $this->nextMarker = NULL; + } + + public function isFinished() + { + return $this->nextMarker == NULL; + } + + public function getSubscriptionNames() + { + return $this->SubscriptionNames; + } + + public function getNextMarker() + { + return $this->nextMarker; + } + + public function parseResponse($statusCode, $content) + { + $this->statusCode = $statusCode; + if ($statusCode != 200) { + $this->parseErrorResponse($statusCode, $content); + return; + } + + $this->succeed = TRUE; + $xmlReader = $this->loadXmlContent($content); + + try { + while ($xmlReader->read()) + { + if ($xmlReader->nodeType == \XMLReader::ELEMENT) + { + switch ($xmlReader->name) { + case 'SubscriptionURL': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $subscriptionName = $this->getSubscriptionNameFromSubscriptionURL($xmlReader->value); + $this->SubscriptionNames[] = $subscriptionName; + } + break; + case 'NextMarker': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $this->nextMarker = $xmlReader->value; + } + break; + } + } + } + } + catch (\Exception $e) + { + throw new MnsException($statusCode, $e->getMessage(), $e); + } + catch (\Throwable $t) + { + throw new MnsException($statusCode, $t->getMessage()); + } + } + + private function getSubscriptionNameFromSubscriptionURL($subscriptionURL) + { + $pieces = explode("/", $subscriptionURL); + if (count($pieces) == 7) + { + return $pieces[6]; + } + return ""; + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + + try + { + $result = XMLParser::parseNormalError($xmlReader); + + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + catch (\Exception $e) + { + if ($exception != NULL) + { + throw $exception; + } + elseif ($e instanceof MnsException) + { + throw $e; + } + else + { + throw new MnsException($statusCode, $e->getMessage()); + } + } + catch (\Throwable $t) + { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} diff --git a/Lib/Alisms/AliyunMNS/Responses/ListTopicResponse.php b/Lib/Alisms/AliyunMNS/Responses/ListTopicResponse.php new file mode 100644 index 0000000..8013c78 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/ListTopicResponse.php @@ -0,0 +1,124 @@ +topicNames = array(); + $this->nextMarker = NULL; + } + + public function isFinished() + { + return $this->nextMarker == NULL; + } + + public function getTopicNames() + { + return $this->topicNames; + } + + public function getNextMarker() + { + return $this->nextMarker; + } + + public function parseResponse($statusCode, $content) + { + $this->statusCode = $statusCode; + if ($statusCode != 200) { + $this->parseErrorResponse($statusCode, $content); + return; + } + + $this->succeed = TRUE; + $xmlReader = $this->loadXmlContent($content); + + try + { + while ($xmlReader->read()) + { + if ($xmlReader->nodeType == \XMLReader::ELEMENT) + { + switch ($xmlReader->name) { + case 'TopicURL': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $topicName = $this->getTopicNameFromTopicURL($xmlReader->value); + $this->topicNames[] = $topicName; + } + break; + case 'NextMarker': + $xmlReader->read(); + if ($xmlReader->nodeType == \XMLReader::TEXT) + { + $this->nextMarker = $xmlReader->value; + } + break; + } + } + } + } + catch (\Exception $e) + { + throw new MnsException($statusCode, $e->getMessage(), $e); + } + catch (\Throwable $t) + { + throw new MnsException($statusCode, $t->getMessage()); + } + } + + private function getTopicNameFromTopicURL($topicURL) + { + $pieces = explode("/", $topicURL); + if (count($pieces) == 5) + { + return $pieces[4]; + } + return ""; + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + + try + { + $result = XMLParser::parseNormalError($xmlReader); + + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + catch (\Exception $e) + { + if ($exception != NULL) + { + throw $exception; + } + elseif ($e instanceof MnsException) + { + throw $e; + } + else + { + throw new MnsException($statusCode, $e->getMessage()); + } + } + catch (\Throwable $t) + { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/MnsPromise.php b/Lib/Alisms/AliyunMNS/Responses/MnsPromise.php new file mode 100644 index 0000000..f40c024 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/MnsPromise.php @@ -0,0 +1,50 @@ +promise = $promise; + $this->response = $response; + } + + public function isCompleted() + { + return $this->promise->getState() != 'pending'; + } + + public function getResponse() + { + return $this->response; + } + + public function wait() + { + try { + $res = $this->promise->wait(); + if ($res instanceof ResponseInterface) + { + $this->response->parseResponse($res->getStatusCode(), $res->getBody()); + } + } catch (TransferException $e) { + $message = $e->getMessage(); + if ($e->hasResponse()) { + $message = $e->getResponse()->getBody(); + } + $this->response->parseErrorResponse($e->getCode(), $message); + } + return $this->response; + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/PeekMessageResponse.php b/Lib/Alisms/AliyunMNS/Responses/PeekMessageResponse.php new file mode 100644 index 0000000..deb3c83 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/PeekMessageResponse.php @@ -0,0 +1,85 @@ +base64 = $base64; + } + + public function setBase64($base64) + { + $this->base64 = $base64; + } + + public function isBase64() + { + return ($this->base64 == TRUE); + } + + public function parseResponse($statusCode, $content) + { + $this->statusCode = $statusCode; + if ($statusCode == 200) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + + $xmlReader = $this->loadXmlContent($content); + + try { + $this->readMessagePropertiesForPeekXML($xmlReader, $this->base64); + } catch (\Exception $e) { + throw new MnsException($statusCode, $e->getMessage(), $e); + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + + try { + $result = XMLParser::parseNormalError($xmlReader); + if ($result['Code'] == Constants::QUEUE_NOT_EXIST) + { + throw new QueueNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::MESSAGE_NOT_EXIST) + { + throw new MessageNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/PublishMessageResponse.php b/Lib/Alisms/AliyunMNS/Responses/PublishMessageResponse.php new file mode 100644 index 0000000..0ee38da --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/PublishMessageResponse.php @@ -0,0 +1,74 @@ +statusCode = $statusCode; + if ($statusCode == 201) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + + $xmlReader = $this->loadXmlContent($content); + try { + $this->readMessageIdAndMD5XML($xmlReader); + } catch (\Exception $e) { + throw new MnsException($statusCode, $e->getMessage(), $e); + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + try { + $result = XMLParser::parseNormalError($xmlReader); + if ($result['Code'] == Constants::TOPIC_NOT_EXIST) + { + throw new TopicNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::INVALID_ARGUMENT) + { + throw new InvalidArgumentException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::MALFORMED_XML) + { + throw new MalformedXMLException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/ReceiveMessageResponse.php b/Lib/Alisms/AliyunMNS/Responses/ReceiveMessageResponse.php new file mode 100644 index 0000000..44aa72c --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/ReceiveMessageResponse.php @@ -0,0 +1,83 @@ +base64 = $base64; + } + + public function setBase64($base64) + { + $this->base64 = $base64; + } + + public function isBase64() + { + return ($this->base64 == TRUE); + } + + public function parseResponse($statusCode, $content) + { + $this->statusCode = $statusCode; + if ($statusCode == 200) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + + $xmlReader = $this->loadXmlContent($content); + try { + $this->readMessagePropertiesForReceiveXML($xmlReader, $this->base64); + } catch (\Exception $e) { + throw new MnsException($statusCode, $e->getMessage(), $e); + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + try { + $result = XMLParser::parseNormalError($xmlReader); + if ($result['Code'] == Constants::QUEUE_NOT_EXIST) + { + throw new QueueNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::MESSAGE_NOT_EXIST) + { + throw new MessageNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/SendMessageResponse.php b/Lib/Alisms/AliyunMNS/Responses/SendMessageResponse.php new file mode 100644 index 0000000..77f1681 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/SendMessageResponse.php @@ -0,0 +1,74 @@ +statusCode = $statusCode; + if ($statusCode == 201) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + + $xmlReader = $this->loadXmlContent($content); + try { + $this->readMessageIdAndMD5XML($xmlReader); + } catch (\Exception $e) { + throw new MnsException($statusCode, $e->getMessage(), $e); + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + try { + $result = XMLParser::parseNormalError($xmlReader); + if ($result['Code'] == Constants::QUEUE_NOT_EXIST) + { + throw new QueueNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::INVALID_ARGUMENT) + { + throw new InvalidArgumentException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::MALFORMED_XML) + { + throw new MalformedXMLException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/SetAccountAttributesResponse.php b/Lib/Alisms/AliyunMNS/Responses/SetAccountAttributesResponse.php new file mode 100644 index 0000000..4d08334 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/SetAccountAttributesResponse.php @@ -0,0 +1,47 @@ +statusCode = $statusCode; + if ($statusCode == 204) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + try { + $result = XMLParser::parseNormalError($xmlReader); + + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/SetQueueAttributeResponse.php b/Lib/Alisms/AliyunMNS/Responses/SetQueueAttributeResponse.php new file mode 100644 index 0000000..5540cd2 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/SetQueueAttributeResponse.php @@ -0,0 +1,57 @@ +statusCode = $statusCode; + if ($statusCode == 204) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + try { + $result = XMLParser::parseNormalError($xmlReader); + + if ($result['Code'] == Constants::INVALID_ARGUMENT) + { + throw new InvalidArgumentException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::QUEUE_NOT_EXIST) + { + throw new QueueNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/SetSubscriptionAttributeResponse.php b/Lib/Alisms/AliyunMNS/Responses/SetSubscriptionAttributeResponse.php new file mode 100644 index 0000000..1d79849 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/SetSubscriptionAttributeResponse.php @@ -0,0 +1,63 @@ +statusCode = $statusCode; + if ($statusCode == 204) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + try { + $result = XMLParser::parseNormalError($xmlReader); + + if ($result['Code'] == Constants::INVALID_ARGUMENT) + { + throw new InvalidArgumentException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::SUBSCRIPTION_NOT_EXIST) + { + throw new SubscriptionNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + catch (\Exception $e) + { + if ($exception != NULL) { + throw $exception; + } + elseif ($e instanceof MnsException) + { + throw $e; + } + else + { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/SetTopicAttributeResponse.php b/Lib/Alisms/AliyunMNS/Responses/SetTopicAttributeResponse.php new file mode 100644 index 0000000..642c823 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/SetTopicAttributeResponse.php @@ -0,0 +1,66 @@ +statusCode = $statusCode; + if ($statusCode == 204) { + $this->succeed = TRUE; + } else { + $this->parseErrorResponse($statusCode, $content); + } + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + try { + $result = XMLParser::parseNormalError($xmlReader); + + if ($result['Code'] == Constants::INVALID_ARGUMENT) + { + throw new InvalidArgumentException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::TOPIC_NOT_EXIST) + { + throw new TopicNotExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + catch (\Exception $e) + { + if ($exception != NULL) + { + throw $exception; + } + elseif ($e instanceof MnsException) + { + throw $e; + } + else + { + throw new MnsException($statusCode, $e->getMessage()); + } + } + catch (\Throwable $t) + { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/SubscribeResponse.php b/Lib/Alisms/AliyunMNS/Responses/SubscribeResponse.php new file mode 100644 index 0000000..8a1fff6 --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/SubscribeResponse.php @@ -0,0 +1,66 @@ +statusCode = $statusCode; + if ($statusCode == 201 || $statusCode == 204) + { + $this->succeed = TRUE; + } + else + { + $this->parseErrorResponse($statusCode, $content); + } + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + try + { + $result = XMLParser::parseNormalError($xmlReader); + + if ($result['Code'] == Constants::INVALID_ARGUMENT) + { + throw new InvalidArgumentException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + if ($result['Code'] == Constants::SUBSCRIPTION_ALREADY_EXIST) + { + throw new SubscriptionAlreadyExistException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } + catch (\Exception $e) + { + if ($exception != NULL) + { + throw $exception; + } + elseif ($e instanceof MnsException) + { + throw $e; + } + else + { + throw new MnsException($statusCode, $e->getMessage()); + } + } + catch (\Throwable $t) + { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Responses/UnsubscribeResponse.php b/Lib/Alisms/AliyunMNS/Responses/UnsubscribeResponse.php new file mode 100644 index 0000000..cfd902e --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Responses/UnsubscribeResponse.php @@ -0,0 +1,48 @@ +statusCode = $statusCode; + if ($statusCode == 204) + { + $this->succeed = TRUE; + } + else + { + $this->parseErrorResponse($statusCode, $content); + } + } + + public function parseErrorResponse($statusCode, $content, MnsException $exception = NULL) + { + $this->succeed = FALSE; + $xmlReader = $this->loadXmlContent($content); + try { + $result = XMLParser::parseNormalError($xmlReader); + throw new MnsException($statusCode, $result['Message'], $exception, $result['Code'], $result['RequestId'], $result['HostId']); + } catch (\Exception $e) { + if ($exception != NULL) { + throw $exception; + } elseif($e instanceof MnsException) { + throw $e; + } else { + throw new MnsException($statusCode, $e->getMessage()); + } + } catch (\Throwable $t) { + throw new MnsException($statusCode, $t->getMessage()); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Signature/Signature.php b/Lib/Alisms/AliyunMNS/Signature/Signature.php new file mode 100644 index 0000000..2ed987e --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Signature/Signature.php @@ -0,0 +1,53 @@ +getHeaders(); + $contentMd5 = ""; + if (isset($headers['Content-MD5'])) + { + $contentMd5 = $headers['Content-MD5']; + } + $contentType = ""; + if (isset($headers['Content-Type'])) + { + $contentType = $headers['Content-Type']; + } + $date = $headers['Date']; + $queryString = $request->getQueryString(); + $canonicalizedResource = $request->getResourcePath(); + if ($queryString != NULL) + { + $canonicalizedResource .= "?" . $request->getQueryString(); + } + if (0 !== strpos($canonicalizedResource, "/")) + { + $canonicalizedResource = "/" . $canonicalizedResource; + } + + $tmpHeaders = array(); + foreach ($headers as $key => $value) + { + if (0 === strpos($key, Constants::MNS_HEADER_PREFIX)) + { + $tmpHeaders[$key] = $value; + } + } + ksort($tmpHeaders); + + $canonicalizedMNSHeaders = implode("\n", array_map(function ($v, $k) { return $k . ":" . $v; }, $tmpHeaders, array_keys($tmpHeaders))); + + $stringToSign = strtoupper($request->getMethod()) . "\n" . $contentMd5 . "\n" . $contentType . "\n" . $date . "\n" . $canonicalizedMNSHeaders . "\n" . $canonicalizedResource; + + return base64_encode(hash_hmac("sha1", $stringToSign, $accessKey, $raw_output = TRUE)); + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Topic.php b/Lib/Alisms/AliyunMNS/Topic.php new file mode 100644 index 0000000..6682b2f --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Topic.php @@ -0,0 +1,128 @@ +client = $client; + $this->topicName = $topicName; + } + + public function getTopicName() + { + return $this->topicName; + } + + public function setAttribute(TopicAttributes $attributes) + { + $request = new SetTopicAttributeRequest($this->topicName, $attributes); + $response = new SetTopicAttributeResponse(); + return $this->client->sendRequest($request, $response); + } + + public function getAttribute() + { + $request = new GetTopicAttributeRequest($this->topicName); + $response = new GetTopicAttributeResponse(); + return $this->client->sendRequest($request, $response); + } + + public function generateQueueEndpoint($queueName) + { + return "acs:mns:" . $this->client->getRegion() . ":" . $this->client->getAccountId() . ":queues/" . $queueName; + } + + public function generateMailEndpoint($mailAddress) + { + return "mail:directmail:" . $mailAddress; + } + + public function generateSmsEndpoint($phone = null) + { + if ($phone) + { + return "sms:directsms:" . $phone; + } + else + { + return "sms:directsms:anonymous"; + } + } + + public function generateBatchSmsEndpoint() + { + return "sms:directsms:anonymous"; + } + + public function publishMessage(PublishMessageRequest $request) + { + $request->setTopicName($this->topicName); + $response = new PublishMessageResponse(); + return $this->client->sendRequest($request, $response); + } + + public function subscribe(SubscriptionAttributes $attributes) + { + $attributes->setTopicName($this->topicName); + $request = new SubscribeRequest($attributes); + $response = new SubscribeResponse(); + return $this->client->sendRequest($request, $response); + } + + public function unsubscribe($subscriptionName) + { + $request = new UnsubscribeRequest($this->topicName, $subscriptionName); + $response = new UnsubscribeResponse(); + return $this->client->sendRequest($request, $response); + } + + public function getSubscriptionAttribute($subscriptionName) + { + $request = new GetSubscriptionAttributeRequest($this->topicName, $subscriptionName); + $response = new GetSubscriptionAttributeResponse(); + return $this->client->sendRequest($request, $response); + } + + public function setSubscriptionAttribute(UpdateSubscriptionAttributes $attributes) + { + $attributes->setTopicName($this->topicName); + $request = new SetSubscriptionAttributeRequest($attributes); + $response = new SetSubscriptionAttributeResponse(); + return $this->client->sendRequest($request, $response); + } + + public function listSubscription($retNum = NULL, $prefix = NULL, $marker = NULL) + { + $request = new ListSubscriptionRequest($this->topicName, $retNum, $prefix, $marker); + $response = new ListSubscriptionResponse(); + return $this->client->sendRequest($request, $response); + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Traits/MessageIdAndMD5.php b/Lib/Alisms/AliyunMNS/Traits/MessageIdAndMD5.php new file mode 100644 index 0000000..19df2bf --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Traits/MessageIdAndMD5.php @@ -0,0 +1,30 @@ +messageId; + } + + public function getMessageBodyMD5() + { + return $this->messageBodyMD5; + } + + public function readMessageIdAndMD5XML(\XMLReader $xmlReader) + { + $message = Message::fromXML($xmlReader, TRUE); + $this->messageId = $message->getMessageId(); + $this->messageBodyMD5 = $message->getMessageBodyMD5(); + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Traits/MessagePropertiesForPeek.php b/Lib/Alisms/AliyunMNS/Traits/MessagePropertiesForPeek.php new file mode 100644 index 0000000..cb1a48a --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Traits/MessagePropertiesForPeek.php @@ -0,0 +1,63 @@ +messageBody; + } + + public function getEnqueueTime() + { + return $this->enqueueTime; + } + + public function getNextVisibleTime() + { + return $this->nextVisibleTime; + } + + public function getFirstDequeueTime() + { + return $this->firstDequeueTime; + } + + public function getDequeueCount() + { + return $this->dequeueCount; + } + + public function getPriority() + { + return $this->priority; + } + + public function readMessagePropertiesForPeekXML(\XMLReader $xmlReader, $base64) + { + $message = Message::fromXML($xmlReader, $base64); + $this->messageId = $message->getMessageId(); + $this->messageBodyMD5 = $message->getMessageBodyMD5(); + $this->messageBody = $message->getMessageBody(); + $this->enqueueTime = $message->getEnqueueTime(); + $this->nextVisibleTime = $message->getNextVisibleTime(); + $this->firstDequeueTime = $message->getFirstDequeueTime(); + $this->dequeueCount = $message->getDequeueCount(); + $this->priority = $message->getPriority(); + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Traits/MessagePropertiesForPublish.php b/Lib/Alisms/AliyunMNS/Traits/MessagePropertiesForPublish.php new file mode 100644 index 0000000..be6e5af --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Traits/MessagePropertiesForPublish.php @@ -0,0 +1,45 @@ +messageBody; + } + + public function setMessageBody($messageBody) + { + $this->messageBody = $messageBody; + } + + public function getMessageAttributes() + { + return $this->messageAttributes; + } + + public function setMessageAttributes($messageAttributes) + { + $this->messageAttributes = $messageAttributes; + } + + public function writeMessagePropertiesForPublishXML(\XMLWriter $xmlWriter) + { + if ($this->messageBody != NULL) + { + $xmlWriter->writeElement(Constants::MESSAGE_BODY, $this->messageBody); + } + if ($this->messageAttributes !== NULL) + { + $this->messageAttributes->writeXML($xmlWriter); + } + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Traits/MessagePropertiesForReceive.php b/Lib/Alisms/AliyunMNS/Traits/MessagePropertiesForReceive.php new file mode 100644 index 0000000..581c97e --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Traits/MessagePropertiesForReceive.php @@ -0,0 +1,33 @@ +receiptHandle; + } + + public function readMessagePropertiesForReceiveXML(\XMLReader $xmlReader, $base64) + { + $message = Message::fromXML($xmlReader, $base64); + $this->messageId = $message->getMessageId(); + $this->messageBodyMD5 = $message->getMessageBodyMD5(); + $this->messageBody = $message->getMessageBody(); + $this->enqueueTime = $message->getEnqueueTime(); + $this->nextVisibleTime = $message->getNextVisibleTime(); + $this->firstDequeueTime = $message->getFirstDequeueTime(); + $this->dequeueCount = $message->getDequeueCount(); + $this->priority = $message->getPriority(); + $this->receiptHandle = $message->getReceiptHandle(); + } +} + +?> diff --git a/Lib/Alisms/AliyunMNS/Traits/MessagePropertiesForSend.php b/Lib/Alisms/AliyunMNS/Traits/MessagePropertiesForSend.php new file mode 100644 index 0000000..c92d07a --- /dev/null +++ b/Lib/Alisms/AliyunMNS/Traits/MessagePropertiesForSend.php @@ -0,0 +1,63 @@ +messageBody; + } + + public function setMessageBody($messageBody) + { + $this->messageBody = $messageBody; + } + + public function getDelaySeconds() + { + return $this->delaySeconds; + } + + public function setDelaySeconds($delaySeconds) + { + $this->delaySeconds = $delaySeconds; + } + + public function getPriority() + { + return $this->priority; + } + + public function setPriority($priority) + { + $this->priority = $priority; + } + + public function writeMessagePropertiesForSendXML(\XMLWriter $xmlWriter, $base64) + { + if ($this->messageBody != NULL) + { + if ($base64 == TRUE) { + $xmlWriter->writeElement(Constants::MESSAGE_BODY, base64_encode($this->messageBody)); + } else { + $xmlWriter->writeElement(Constants::MESSAGE_BODY, $this->messageBody); + } + } + if ($this->delaySeconds != NULL) + { + $xmlWriter->writeElement(Constants::DELAY_SECONDS, $this->delaySeconds); + } + if ($this->priority !== NULL) + { + $xmlWriter->writeElement(Constants::PRIORITY, $this->priority); + } + } +} + +?> diff --git a/Lib/Alisms/GuzzleHttp/Client.php b/Lib/Alisms/GuzzleHttp/Client.php new file mode 100644 index 0000000..2e86ece --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Client.php @@ -0,0 +1,397 @@ + 'http://www.foo.com/1.0/', + * 'timeout' => 0, + * 'allow_redirects' => false, + * 'proxy' => '192.168.16.1:10' + * ]); + * + * Client configuration settings include the following options: + * + * - handler: (callable) Function that transfers HTTP requests over the + * wire. The function is called with a Psr7\Http\Message\RequestInterface + * and array of transfer options, and must return a + * GuzzleHttp\Promise\PromiseInterface that is fulfilled with a + * Psr7\Http\Message\ResponseInterface on success. "handler" is a + * constructor only option that cannot be overridden in per/request + * options. If no handler is provided, a default handler will be created + * that enables all of the request options below by attaching all of the + * default middleware to the handler. + * - base_uri: (string|UriInterface) Base URI of the client that is merged + * into relative URIs. Can be a string or instance of UriInterface. + * - **: any request option + * + * @param array $config Client configuration settings. + * + * @see \GuzzleHttp\RequestOptions for a list of available request options. + */ + public function __construct(array $config = []) + { + if (!isset($config['handler'])) { + $config['handler'] = HandlerStack::create(); + } + + // Convert the base_uri to a UriInterface + if (isset($config['base_uri'])) { + $config['base_uri'] = Psr7\uri_for($config['base_uri']); + } + + $this->configureDefaults($config); + } + + public function __call($method, $args) + { + if (count($args) < 1) { + throw new \InvalidArgumentException('Magic request methods require a URI and optional options array'); + } + + $uri = $args[0]; + $opts = isset($args[1]) ? $args[1] : []; + + return substr($method, -5) === 'Async' + ? $this->requestAsync(substr($method, 0, -5), $uri, $opts) + : $this->request($method, $uri, $opts); + } + + public function sendAsync(RequestInterface $request, array $options = []) + { + // Merge the base URI into the request URI if needed. + $options = $this->prepareDefaults($options); + + return $this->transfer( + $request->withUri($this->buildUri($request->getUri(), $options)), + $options + ); + } + + public function send(RequestInterface $request, array $options = []) + { + $options[RequestOptions::SYNCHRONOUS] = true; + return $this->sendAsync($request, $options)->wait(); + } + + public function requestAsync($method, $uri = null, array $options = []) + { + $options = $this->prepareDefaults($options); + // Remove request modifying parameter because it can be done up-front. + $headers = isset($options['headers']) ? $options['headers'] : []; + $body = isset($options['body']) ? $options['body'] : null; + $version = isset($options['version']) ? $options['version'] : '1.1'; + // Merge the URI into the base URI. + $uri = $this->buildUri($uri, $options); + if (is_array($body)) { + $this->invalidBody(); + } + $request = new Psr7\Request($method, $uri, $headers, $body, $version); + // Remove the option so that they are not doubly-applied. + unset($options['headers'], $options['body'], $options['version']); + + return $this->transfer($request, $options); + } + + public function request($method, $uri = null, array $options = []) + { + $options[RequestOptions::SYNCHRONOUS] = true; + return $this->requestAsync($method, $uri, $options)->wait(); + } + + public function getConfig($option = null) + { + return $option === null + ? $this->config + : (isset($this->config[$option]) ? $this->config[$option] : null); + } + + private function buildUri($uri, array $config) + { + if (!isset($config['base_uri'])) { + return $uri instanceof UriInterface ? $uri : new Psr7\Uri($uri); + } + + return Psr7\Uri::resolve(Psr7\uri_for($config['base_uri']), $uri); + } + + /** + * Configures the default options for a client. + * + * @param array $config + */ + private function configureDefaults(array $config) + { + $defaults = [ + 'allow_redirects' => RedirectMiddleware::$defaultSettings, + 'http_errors' => true, + 'decode_content' => true, + 'verify' => true, + 'cookies' => false + ]; + + // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set + if ($proxy = getenv('HTTP_PROXY')) { + $defaults['proxy']['http'] = $proxy; + } + + if ($proxy = getenv('HTTPS_PROXY')) { + $defaults['proxy']['https'] = $proxy; + } + + if ($noProxy = getenv('NO_PROXY')) { + $cleanedNoProxy = str_replace(' ', '', $noProxy); + $defaults['proxy']['no'] = explode(',', $cleanedNoProxy); + } + + $this->config = $config + $defaults; + + if (!empty($config['cookies']) && $config['cookies'] === true) { + $this->config['cookies'] = new CookieJar(); + } + + // Add the default user-agent header. + if (!isset($this->config['headers'])) { + $this->config['headers'] = ['User-Agent' => default_user_agent()]; + } else { + // Add the User-Agent header if one was not already set. + foreach (array_keys($this->config['headers']) as $name) { + if (strtolower($name) === 'user-agent') { + return; + } + } + $this->config['headers']['User-Agent'] = default_user_agent(); + } + } + + /** + * Merges default options into the array. + * + * @param array $options Options to modify by reference + * + * @return array + */ + private function prepareDefaults($options) + { + $defaults = $this->config; + + if (!empty($defaults['headers'])) { + // Default headers are only added if they are not present. + $defaults['_conditional'] = $defaults['headers']; + unset($defaults['headers']); + } + + // Special handling for headers is required as they are added as + // conditional headers and as headers passed to a request ctor. + if (array_key_exists('headers', $options)) { + // Allows default headers to be unset. + if ($options['headers'] === null) { + $defaults['_conditional'] = null; + unset($options['headers']); + } elseif (!is_array($options['headers'])) { + throw new \InvalidArgumentException('headers must be an array'); + } + } + + // Shallow merge defaults underneath options. + $result = $options + $defaults; + + // Remove null values. + foreach ($result as $k => $v) { + if ($v === null) { + unset($result[$k]); + } + } + + return $result; + } + + /** + * Transfers the given request and applies request options. + * + * The URI of the request is not modified and the request options are used + * as-is without merging in default options. + * + * @param RequestInterface $request + * @param array $options + * + * @return Promise\PromiseInterface + */ + private function transfer(RequestInterface $request, array $options) + { + // save_to -> sink + if (isset($options['save_to'])) { + $options['sink'] = $options['save_to']; + unset($options['save_to']); + } + + // exceptions -> http_error + if (isset($options['exceptions'])) { + $options['http_errors'] = $options['exceptions']; + unset($options['exceptions']); + } + + $request = $this->applyOptions($request, $options); + $handler = $options['handler']; + + try { + return Promise\promise_for($handler($request, $options)); + } catch (\Exception $e) { + return Promise\rejection_for($e); + } + } + + /** + * Applies the array of request options to a request. + * + * @param RequestInterface $request + * @param array $options + * + * @return RequestInterface + */ + private function applyOptions(RequestInterface $request, array &$options) + { + $modify = []; + + if (isset($options['form_params'])) { + if (isset($options['multipart'])) { + throw new \InvalidArgumentException('You cannot use ' + . 'form_params and multipart at the same time. Use the ' + . 'form_params option if you want to send application/' + . 'x-www-form-urlencoded requests, and the multipart ' + . 'option to send multipart/form-data requests.'); + } + $options['body'] = http_build_query($options['form_params'], null, '&'); + unset($options['form_params']); + $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded'; + } + + if (isset($options['multipart'])) { + $elements = $options['multipart']; + unset($options['multipart']); + $options['body'] = new Psr7\MultipartStream($elements); + } + + if (!empty($options['decode_content']) + && $options['decode_content'] !== true + ) { + $modify['set_headers']['Accept-Encoding'] = $options['decode_content']; + } + + if (isset($options['headers'])) { + if (isset($modify['set_headers'])) { + $modify['set_headers'] = $options['headers'] + $modify['set_headers']; + } else { + $modify['set_headers'] = $options['headers']; + } + unset($options['headers']); + } + + if (isset($options['body'])) { + if (is_array($options['body'])) { + $this->invalidBody(); + } + $modify['body'] = Psr7\stream_for($options['body']); + unset($options['body']); + } + + if (!empty($options['auth'])) { + $value = $options['auth']; + $type = is_array($value) + ? (isset($value[2]) ? strtolower($value[2]) : 'basic') + : $value; + $config['auth'] = $value; + switch (strtolower($type)) { + case 'basic': + $modify['set_headers']['Authorization'] = 'Basic ' + . base64_encode("$value[0]:$value[1]"); + break; + case 'digest': + // @todo: Do not rely on curl + $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST; + $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]"; + break; + } + } + + if (isset($options['query'])) { + $value = $options['query']; + if (is_array($value)) { + $value = http_build_query($value, null, '&', PHP_QUERY_RFC3986); + } + if (!is_string($value)) { + throw new \InvalidArgumentException('query must be a string or array'); + } + $modify['query'] = $value; + unset($options['query']); + } + + if (isset($options['json'])) { + $modify['body'] = Psr7\stream_for(json_encode($options['json'])); + $options['_conditional']['Content-Type'] = 'application/json'; + unset($options['json']); + } + + $request = Psr7\modify_request($request, $modify); + if ($request->getBody() instanceof Psr7\MultipartStream) { + // Use a multipart/form-data POST if a Content-Type is not set. + $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary=' + . $request->getBody()->getBoundary(); + } + + // Merge in conditional headers if they are not present. + if (isset($options['_conditional'])) { + // Build up the changes so it's in a single clone of the message. + $modify = []; + foreach ($options['_conditional'] as $k => $v) { + if (!$request->hasHeader($k)) { + $modify['set_headers'][$k] = $v; + } + } + $request = Psr7\modify_request($request, $modify); + // Don't pass this internal value along to middleware/handlers. + unset($options['_conditional']); + } + + return $request; + } + + private function invalidBody() + { + throw new \InvalidArgumentException('Passing in the "body" request ' + . 'option as an array to send a POST request has been deprecated. ' + . 'Please use the "form_params" request option to send a ' + . 'application/x-www-form-urlencoded request, or a the "multipart" ' + . 'request option to send a multipart/form-data request.'); + } +} diff --git a/Lib/Alisms/GuzzleHttp/ClientInterface.php b/Lib/Alisms/GuzzleHttp/ClientInterface.php new file mode 100644 index 0000000..b5b2306 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/ClientInterface.php @@ -0,0 +1,84 @@ +strictMode = $strictMode; + + foreach ($cookieArray as $cookie) { + if (!($cookie instanceof SetCookie)) { + $cookie = new SetCookie($cookie); + } + $this->setCookie($cookie); + } + } + + /** + * Create a new Cookie jar from an associative array and domain. + * + * @param array $cookies Cookies to create the jar from + * @param string $domain Domain to set the cookies to + * + * @return self + */ + public static function fromArray(array $cookies, $domain) + { + $cookieJar = new self(); + foreach ($cookies as $name => $value) { + $cookieJar->setCookie(new SetCookie([ + 'Domain' => $domain, + 'Name' => $name, + 'Value' => $value, + 'Discard' => true + ])); + } + + return $cookieJar; + } + + /** + * Quote the cookie value if it is not already quoted and it contains + * problematic characters. + * + * @param string $value Value that may or may not need to be quoted + * + * @return string + */ + public static function getCookieValue($value) + { + if (substr($value, 0, 1) !== '"' && + substr($value, -1, 1) !== '"' && + strpbrk($value, ';,=') + ) { + $value = '"' . $value . '"'; + } + + return $value; + } + + /** + * Evaluate if this cookie should be persisted to storage + * that survives between requests. + * + * @param SetCookie $cookie Being evaluated. + * @param bool $allowSessionCookies If we should presist session cookies + * @return bool + */ + public static function shouldPersist( + SetCookie $cookie, + $allowSessionCookies = false + ) { + if ($cookie->getExpires() || $allowSessionCookies) { + if (!$cookie->getDiscard()) { + return true; + } + } + + return false; + } + + public function toArray() + { + return array_map(function (SetCookie $cookie) { + return $cookie->toArray(); + }, $this->getIterator()->getArrayCopy()); + } + + public function clear($domain = null, $path = null, $name = null) + { + if (!$domain) { + $this->cookies = []; + return; + } elseif (!$path) { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain) { + return !$cookie->matchesDomain($domain); + } + ); + } elseif (!$name) { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain) { + return !($cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } else { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain, $name) { + return !($cookie->getName() == $name && + $cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } + } + + public function clearSessionCookies() + { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) { + return !$cookie->getDiscard() && $cookie->getExpires(); + } + ); + } + + public function setCookie(SetCookie $cookie) + { + // If the name string is empty (but not 0), ignore the set-cookie + // string entirely. + $name = $cookie->getName(); + if (!$name && $name !== '0') { + return false; + } + + // Only allow cookies with set and valid domain, name, value + $result = $cookie->validate(); + if ($result !== true) { + if ($this->strictMode) { + throw new \RuntimeException('Invalid cookie: ' . $result); + } else { + $this->removeCookieIfEmpty($cookie); + return false; + } + } + + // Resolve conflicts with previously set cookies + foreach ($this->cookies as $i => $c) { + + // Two cookies are identical, when their path, and domain are + // identical. + if ($c->getPath() != $cookie->getPath() || + $c->getDomain() != $cookie->getDomain() || + $c->getName() != $cookie->getName() + ) { + continue; + } + + // The previously set cookie is a discard cookie and this one is + // not so allow the new cookie to be set + if (!$cookie->getDiscard() && $c->getDiscard()) { + unset($this->cookies[$i]); + continue; + } + + // If the new cookie's expiration is further into the future, then + // replace the old cookie + if ($cookie->getExpires() > $c->getExpires()) { + unset($this->cookies[$i]); + continue; + } + + // If the value has changed, we better change it + if ($cookie->getValue() !== $c->getValue()) { + unset($this->cookies[$i]); + continue; + } + + // The cookie exists, so no need to continue + return false; + } + + $this->cookies[] = $cookie; + + return true; + } + + public function count() + { + return count($this->cookies); + } + + public function getIterator() + { + return new \ArrayIterator(array_values($this->cookies)); + } + + public function extractCookies( + RequestInterface $request, + ResponseInterface $response + ) { + if ($cookieHeader = $response->getHeader('Set-Cookie')) { + foreach ($cookieHeader as $cookie) { + $sc = SetCookie::fromString($cookie); + if (!$sc->getDomain()) { + $sc->setDomain($request->getUri()->getHost()); + } + $this->setCookie($sc); + } + } + } + + public function withCookieHeader(RequestInterface $request) + { + $values = []; + $uri = $request->getUri(); + $scheme = $uri->getScheme(); + $host = $uri->getHost(); + $path = $uri->getPath() ?: '/'; + + foreach ($this->cookies as $cookie) { + if ($cookie->matchesPath($path) && + $cookie->matchesDomain($host) && + !$cookie->isExpired() && + (!$cookie->getSecure() || $scheme == 'https') + ) { + $values[] = $cookie->getName() . '=' + . self::getCookieValue($cookie->getValue()); + } + } + + return $values + ? $request->withHeader('Cookie', implode('; ', $values)) + : $request; + } + + /** + * If a cookie already exists and the server asks to set it again with a + * null value, the cookie must be deleted. + * + * @param SetCookie $cookie + */ + private function removeCookieIfEmpty(SetCookie $cookie) + { + $cookieValue = $cookie->getValue(); + if ($cookieValue === null || $cookieValue === '') { + $this->clear( + $cookie->getDomain(), + $cookie->getPath(), + $cookie->getName() + ); + } + } +} diff --git a/Lib/Alisms/GuzzleHttp/Cookie/CookieJarInterface.php b/Lib/Alisms/GuzzleHttp/Cookie/CookieJarInterface.php new file mode 100644 index 0000000..2cf298a --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Cookie/CookieJarInterface.php @@ -0,0 +1,84 @@ +filename = $cookieFile; + $this->storeSessionCookies = $storeSessionCookies; + + if (file_exists($cookieFile)) { + $this->load($cookieFile); + } + } + + /** + * Saves the file when shutting down + */ + public function __destruct() + { + $this->save($this->filename); + } + + /** + * Saves the cookies to a file. + * + * @param string $filename File to save + * @throws \RuntimeException if the file cannot be found or created + */ + public function save($filename) + { + $json = []; + foreach ($this as $cookie) { + /** @var SetCookie $cookie */ + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + if (false === file_put_contents($filename, json_encode($json))) { + throw new \RuntimeException("Unable to save file {$filename}"); + } + } + + /** + * Load cookies from a JSON formatted file. + * + * Old cookies are kept unless overwritten by newly loaded ones. + * + * @param string $filename Cookie file to load. + * @throws \RuntimeException if the file cannot be loaded. + */ + public function load($filename) + { + $json = file_get_contents($filename); + if (false === $json) { + throw new \RuntimeException("Unable to load file {$filename}"); + } + + $data = json_decode($json, true); + if (is_array($data)) { + foreach (json_decode($json, true) as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (strlen($data)) { + throw new \RuntimeException("Invalid cookie file: {$filename}"); + } + } +} diff --git a/Lib/Alisms/GuzzleHttp/Cookie/SessionCookieJar.php b/Lib/Alisms/GuzzleHttp/Cookie/SessionCookieJar.php new file mode 100644 index 0000000..d80c480 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Cookie/SessionCookieJar.php @@ -0,0 +1,72 @@ +sessionKey = $sessionKey; + $this->storeSessionCookies = $storeSessionCookies; + $this->load(); + } + + /** + * Saves cookies to session when shutting down + */ + public function __destruct() + { + $this->save(); + } + + /** + * Save cookies to the client session + */ + public function save() + { + $json = []; + foreach ($this as $cookie) { + /** @var SetCookie $cookie */ + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $_SESSION[$this->sessionKey] = json_encode($json); + } + + /** + * Load the contents of the client session into the data array + */ + protected function load() + { + $cookieJar = isset($_SESSION[$this->sessionKey]) + ? $_SESSION[$this->sessionKey] + : null; + + $data = json_decode($cookieJar, true); + if (is_array($data)) { + foreach ($data as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (strlen($data)) { + throw new \RuntimeException("Invalid cookie data"); + } + } +} diff --git a/Lib/Alisms/GuzzleHttp/Cookie/SetCookie.php b/Lib/Alisms/GuzzleHttp/Cookie/SetCookie.php new file mode 100644 index 0000000..acd654d --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Cookie/SetCookie.php @@ -0,0 +1,404 @@ + null, + 'Value' => null, + 'Domain' => null, + 'Path' => '/', + 'Max-Age' => null, + 'Expires' => null, + 'Secure' => false, + 'Discard' => false, + 'HttpOnly' => false + ]; + + /** @var array Cookie data */ + private $data; + + /** + * Create a new SetCookie object from a string + * + * @param string $cookie Set-Cookie header string + * + * @return self + */ + public static function fromString($cookie) + { + // Create the default return array + $data = self::$defaults; + // Explode the cookie string using a series of semicolons + $pieces = array_filter(array_map('trim', explode(';', $cookie))); + // The name of the cookie (first kvp) must include an equal sign. + if (empty($pieces) || !strpos($pieces[0], '=')) { + return new self($data); + } + + // Add the cookie pieces into the parsed data array + foreach ($pieces as $part) { + + $cookieParts = explode('=', $part, 2); + $key = trim($cookieParts[0]); + $value = isset($cookieParts[1]) + ? trim($cookieParts[1], " \n\r\t\0\x0B") + : true; + + // Only check for non-cookies when cookies have been found + if (empty($data['Name'])) { + $data['Name'] = $key; + $data['Value'] = $value; + } else { + foreach (array_keys(self::$defaults) as $search) { + if (!strcasecmp($search, $key)) { + $data[$search] = $value; + continue 2; + } + } + $data[$key] = $value; + } + } + + return new self($data); + } + + /** + * @param array $data Array of cookie data provided by a Cookie parser + */ + public function __construct(array $data = []) + { + $this->data = array_replace(self::$defaults, $data); + // Extract the Expires value and turn it into a UNIX timestamp if needed + if (!$this->getExpires() && $this->getMaxAge()) { + // Calculate the Expires date + $this->setExpires(time() + $this->getMaxAge()); + } elseif ($this->getExpires() && !is_numeric($this->getExpires())) { + $this->setExpires($this->getExpires()); + } + } + + public function __toString() + { + $str = $this->data['Name'] . '=' . $this->data['Value'] . '; '; + foreach ($this->data as $k => $v) { + if ($k != 'Name' && $k != 'Value' && $v !== null && $v !== false) { + if ($k == 'Expires') { + $str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; '; + } else { + $str .= ($v === true ? $k : "{$k}={$v}") . '; '; + } + } + } + + return rtrim($str, '; '); + } + + public function toArray() + { + return $this->data; + } + + /** + * Get the cookie name + * + * @return string + */ + public function getName() + { + return $this->data['Name']; + } + + /** + * Set the cookie name + * + * @param string $name Cookie name + */ + public function setName($name) + { + $this->data['Name'] = $name; + } + + /** + * Get the cookie value + * + * @return string + */ + public function getValue() + { + return $this->data['Value']; + } + + /** + * Set the cookie value + * + * @param string $value Cookie value + */ + public function setValue($value) + { + $this->data['Value'] = $value; + } + + /** + * Get the domain + * + * @return string|null + */ + public function getDomain() + { + return $this->data['Domain']; + } + + /** + * Set the domain of the cookie + * + * @param string $domain + */ + public function setDomain($domain) + { + $this->data['Domain'] = $domain; + } + + /** + * Get the path + * + * @return string + */ + public function getPath() + { + return $this->data['Path']; + } + + /** + * Set the path of the cookie + * + * @param string $path Path of the cookie + */ + public function setPath($path) + { + $this->data['Path'] = $path; + } + + /** + * Maximum lifetime of the cookie in seconds + * + * @return int|null + */ + public function getMaxAge() + { + return $this->data['Max-Age']; + } + + /** + * Set the max-age of the cookie + * + * @param int $maxAge Max age of the cookie in seconds + */ + public function setMaxAge($maxAge) + { + $this->data['Max-Age'] = $maxAge; + } + + /** + * The UNIX timestamp when the cookie Expires + * + * @return mixed + */ + public function getExpires() + { + return $this->data['Expires']; + } + + /** + * Set the unix timestamp for which the cookie will expire + * + * @param int $timestamp Unix timestamp + */ + public function setExpires($timestamp) + { + $this->data['Expires'] = is_numeric($timestamp) + ? (int) $timestamp + : strtotime($timestamp); + } + + /** + * Get whether or not this is a secure cookie + * + * @return null|bool + */ + public function getSecure() + { + return $this->data['Secure']; + } + + /** + * Set whether or not the cookie is secure + * + * @param bool $secure Set to true or false if secure + */ + public function setSecure($secure) + { + $this->data['Secure'] = $secure; + } + + /** + * Get whether or not this is a session cookie + * + * @return null|bool + */ + public function getDiscard() + { + return $this->data['Discard']; + } + + /** + * Set whether or not this is a session cookie + * + * @param bool $discard Set to true or false if this is a session cookie + */ + public function setDiscard($discard) + { + $this->data['Discard'] = $discard; + } + + /** + * Get whether or not this is an HTTP only cookie + * + * @return bool + */ + public function getHttpOnly() + { + return $this->data['HttpOnly']; + } + + /** + * Set whether or not this is an HTTP only cookie + * + * @param bool $httpOnly Set to true or false if this is HTTP only + */ + public function setHttpOnly($httpOnly) + { + $this->data['HttpOnly'] = $httpOnly; + } + + /** + * Check if the cookie matches a path value. + * + * A request-path path-matches a given cookie-path if at least one of + * the following conditions holds: + * + * - The cookie-path and the request-path are identical. + * - The cookie-path is a prefix of the request-path, and the last + * character of the cookie-path is %x2F ("/"). + * - The cookie-path is a prefix of the request-path, and the first + * character of the request-path that is not included in the cookie- + * path is a %x2F ("/") character. + * + * @param string $requestPath Path to check against + * + * @return bool + */ + public function matchesPath($requestPath) + { + $cookiePath = $this->getPath(); + + // Match on exact matches or when path is the default empty "/" + if ($cookiePath == '/' || $cookiePath == $requestPath) { + return true; + } + + // Ensure that the cookie-path is a prefix of the request path. + if (0 !== strpos($requestPath, $cookiePath)) { + return false; + } + + // Match if the last character of the cookie-path is "/" + if (substr($cookiePath, -1, 1) == '/') { + return true; + } + + // Match if the first character not included in cookie path is "/" + return substr($requestPath, strlen($cookiePath), 1) == '/'; + } + + /** + * Check if the cookie matches a domain value + * + * @param string $domain Domain to check against + * + * @return bool + */ + public function matchesDomain($domain) + { + // Remove the leading '.' as per spec in RFC 6265. + // http://tools.ietf.org/html/rfc6265#section-5.2.3 + $cookieDomain = ltrim($this->getDomain(), '.'); + + // Domain not set or exact match. + if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) { + return true; + } + + // Matching the subdomain according to RFC 6265. + // http://tools.ietf.org/html/rfc6265#section-5.1.3 + if (filter_var($domain, FILTER_VALIDATE_IP)) { + return false; + } + + return (bool) preg_match('/\.' . preg_quote($cookieDomain) . '$/', $domain); + } + + /** + * Check if the cookie is expired + * + * @return bool + */ + public function isExpired() + { + return $this->getExpires() && time() > $this->getExpires(); + } + + /** + * Check if the cookie is valid according to RFC 6265 + * + * @return bool|string Returns true if valid or an error message if invalid + */ + public function validate() + { + // Names must not be empty, but can be 0 + $name = $this->getName(); + if (empty($name) && !is_numeric($name)) { + return 'The cookie name must not be empty'; + } + + // Check if any of the invalid characters are present in the cookie name + if (preg_match( + '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/', + $name) + ) { + return 'Cookie name must not contain invalid characters: ASCII ' + . 'Control characters (0-31;127), space, tab and the ' + . 'following characters: ()<>@,;:\"/?={}'; + } + + // Value must not be empty, but can be 0 + $value = $this->getValue(); + if (empty($value) && !is_numeric($value)) { + return 'The cookie value must not be empty'; + } + + // Domains must not be empty, but can be 0 + // A "0" is not a valid internet domain, but may be used as server name + // in a private network. + $domain = $this->getDomain(); + if (empty($domain) && !is_numeric($domain)) { + return 'The cookie domain must not be empty'; + } + + return true; + } +} diff --git a/Lib/Alisms/GuzzleHttp/Exception/BadResponseException.php b/Lib/Alisms/GuzzleHttp/Exception/BadResponseException.php new file mode 100644 index 0000000..fd78431 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Exception/BadResponseException.php @@ -0,0 +1,7 @@ +getStatusCode() + : 0; + parent::__construct($message, $code, $previous); + $this->request = $request; + $this->response = $response; + $this->handlerContext = $handlerContext; + } + + /** + * Wrap non-RequestExceptions with a RequestException + * + * @param RequestInterface $request + * @param \Exception $e + * + * @return RequestException + */ + public static function wrapException(RequestInterface $request, \Exception $e) + { + return $e instanceof RequestException + ? $e + : new RequestException($e->getMessage(), $request, null, $e); + } + + /** + * Factory method to create a new exception with a normalized error message + * + * @param RequestInterface $request Request + * @param ResponseInterface $response Response received + * @param \Exception $previous Previous exception + * @param array $ctx Optional handler context. + * + * @return self + */ + public static function create( + RequestInterface $request, + ResponseInterface $response = null, + \Exception $previous = null, + array $ctx = [] + ) { + if (!$response) { + return new self( + 'Error completing request', + $request, + null, + $previous, + $ctx + ); + } + + $level = floor($response->getStatusCode() / 100); + if ($level == '4') { + $label = 'Client error response'; + $className = __NAMESPACE__ . '\\ClientException'; + } elseif ($level == '5') { + $label = 'Server error response'; + $className = __NAMESPACE__ . '\\ServerException'; + } else { + $label = 'Unsuccessful response'; + $className = __CLASS__; + } + + $message = $label . ' [url] ' . $request->getUri() + . ' [http method] ' . $request->getMethod() + . ' [status code] ' . $response->getStatusCode() + . ' [reason phrase] ' . $response->getReasonPhrase(); + + return new $className($message, $request, $response, $previous, $ctx); + } + + /** + * Get the request that caused the exception + * + * @return RequestInterface + */ + public function getRequest() + { + return $this->request; + } + + /** + * Get the associated response + * + * @return ResponseInterface|null + */ + public function getResponse() + { + return $this->response; + } + + /** + * Check if a response was received + * + * @return bool + */ + public function hasResponse() + { + return $this->response !== null; + } + + /** + * Get contextual information about the error from the underlying handler. + * + * The contents of this array will vary depending on which handler you are + * using. It may also be just an empty array. Relying on this data will + * couple you to a specific handler, but can give more debug information + * when needed. + * + * @return array + */ + public function getHandlerContext() + { + return $this->handlerContext; + } +} diff --git a/Lib/Alisms/GuzzleHttp/Exception/SeekException.php b/Lib/Alisms/GuzzleHttp/Exception/SeekException.php new file mode 100644 index 0000000..a77c289 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Exception/SeekException.php @@ -0,0 +1,27 @@ +stream = $stream; + $msg = $msg ?: 'Could not seek the stream to position ' . $pos; + parent::__construct($msg); + } + + /** + * @return StreamInterface + */ + public function getStream() + { + return $this->stream; + } +} diff --git a/Lib/Alisms/GuzzleHttp/Exception/ServerException.php b/Lib/Alisms/GuzzleHttp/Exception/ServerException.php new file mode 100644 index 0000000..7cdd340 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Exception/ServerException.php @@ -0,0 +1,7 @@ +maxHandles = $maxHandles; + } + + public function create(RequestInterface $request, array $options) + { + if (isset($options['curl']['body_as_string'])) { + $options['_body_as_string'] = $options['curl']['body_as_string']; + unset($options['curl']['body_as_string']); + } + + $easy = new EasyHandle; + $easy->request = $request; + $easy->options = $options; + $conf = $this->getDefaultConf($easy); + $this->applyMethod($easy, $conf); + $this->applyHandlerOptions($easy, $conf); + $this->applyHeaders($easy, $conf); + unset($conf['_headers']); + + // Add handler options from the request configuration options + if (isset($options['curl'])) { + $conf = array_replace($conf, $options['curl']); + } + + $conf[CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy); + $easy->handle = $this->handles + ? array_pop($this->handles) + : curl_init(); + curl_setopt_array($easy->handle, $conf); + + return $easy; + } + + public function release(EasyHandle $easy) + { + $resource = $easy->handle; + unset($easy->handle); + + if (count($this->handles) >= $this->maxHandles) { + curl_close($resource); + } else { + // Remove all callback functions as they can hold onto references + // and are not cleaned up by curl_reset. Using curl_setopt_array + // does not work for some reason, so removing each one + // individually. + curl_setopt($resource, CURLOPT_HEADERFUNCTION, null); + curl_setopt($resource, CURLOPT_READFUNCTION, null); + curl_setopt($resource, CURLOPT_WRITEFUNCTION, null); + curl_setopt($resource, CURLOPT_PROGRESSFUNCTION, null); + curl_reset($resource); + $this->handles[] = $resource; + } + } + + /** + * Completes a cURL transaction, either returning a response promise or a + * rejected promise. + * + * @param callable $handler + * @param EasyHandle $easy + * @param CurlFactoryInterface $factory Dictates how the handle is released + * + * @return \GuzzleHttp\Promise\PromiseInterface + */ + public static function finish( + callable $handler, + EasyHandle $easy, + CurlFactoryInterface $factory + ) { + if (isset($easy->options['on_stats'])) { + self::invokeStats($easy); + } + + if (!$easy->response || $easy->errno) { + return self::finishError($handler, $easy, $factory); + } + + // Return the response if it is present and there is no error. + $factory->release($easy); + + // Rewind the body of the response if possible. + $body = $easy->response->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + + return new FulfilledPromise($easy->response); + } + + private static function invokeStats(EasyHandle $easy) + { + $curlStats = curl_getinfo($easy->handle); + $stats = new TransferStats( + $easy->request, + $easy->response, + $curlStats['total_time'], + $easy->errno, + $curlStats + ); + call_user_func($easy->options['on_stats'], $stats); + } + + private static function finishError( + callable $handler, + EasyHandle $easy, + CurlFactoryInterface $factory + ) { + // Get error information and release the handle to the factory. + $ctx = [ + 'errno' => $easy->errno, + 'error' => curl_error($easy->handle), + ] + curl_getinfo($easy->handle); + $factory->release($easy); + + // Retry when nothing is present or when curl failed to rewind. + if (empty($easy->options['_err_message']) + && (!$easy->errno || $easy->errno == 65) + ) { + return self::retryFailedRewind($handler, $easy, $ctx); + } + + return self::createRejection($easy, $ctx); + } + + private static function createRejection(EasyHandle $easy, array $ctx) + { + static $connectionErrors = [ + CURLE_OPERATION_TIMEOUTED => true, + CURLE_COULDNT_RESOLVE_HOST => true, + CURLE_COULDNT_CONNECT => true, + CURLE_SSL_CONNECT_ERROR => true, + CURLE_GOT_NOTHING => true, + ]; + + // If an exception was encountered during the onHeaders event, then + // return a rejected promise that wraps that exception. + if ($easy->onHeadersException) { + return new RejectedPromise( + new RequestException( + 'An error was encountered during the on_headers event', + $easy->request, + $easy->response, + $easy->onHeadersException, + $ctx + ) + ); + } + + $message = sprintf( + 'cURL error %s: %s (%s)', + $ctx['errno'], + $ctx['error'], + 'see http://curl.haxx.se/libcurl/c/libcurl-errors.html' + ); + + // Create a connection exception if it was a specific error code. + $error = isset($connectionErrors[$easy->errno]) + ? new ConnectException($message, $easy->request, null, $ctx) + : new RequestException($message, $easy->request, $easy->response, null, $ctx); + + return new RejectedPromise($error); + } + + private function getDefaultConf(EasyHandle $easy) + { + $conf = [ + '_headers' => $easy->request->getHeaders(), + CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(), + CURLOPT_URL => (string) $easy->request->getUri(), + CURLOPT_RETURNTRANSFER => false, + CURLOPT_HEADER => false, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_NOSIGNAL => true, + ]; + + if (defined('CURLOPT_PROTOCOLS')) { + $conf[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + $version = $easy->request->getProtocolVersion(); + if ($version == 1.1) { + $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; + } elseif ($version == 2.0) { + $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0; + } else { + $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; + } + + return $conf; + } + + private function applyMethod(EasyHandle $easy, array &$conf) + { + $body = $easy->request->getBody(); + $size = $body->getSize(); + + if ($size === null || $size > 0) { + $this->applyBody($easy->request, $easy->options, $conf); + return; + } + + $method = $easy->request->getMethod(); + if ($method === 'PUT' || $method === 'POST') { + // See http://tools.ietf.org/html/rfc7230#section-3.3.2 + if (!$easy->request->hasHeader('Content-Length')) { + $conf[CURLOPT_HTTPHEADER][] = 'Content-Length: 0'; + } + } elseif ($method === 'HEAD') { + $conf[CURLOPT_NOBODY] = true; + unset( + $conf[CURLOPT_WRITEFUNCTION], + $conf[CURLOPT_READFUNCTION], + $conf[CURLOPT_FILE], + $conf[CURLOPT_INFILE] + ); + } + } + + private function applyBody(RequestInterface $request, array $options, array &$conf) + { + $size = $request->hasHeader('Content-Length') + ? (int) $request->getHeaderLine('Content-Length') + : null; + + // Send the body as a string if the size is less than 1MB OR if the + // [curl][body_as_string] request value is set. + if (($size !== null && $size < 1000000) || + !empty($options['_body_as_string']) + ) { + $conf[CURLOPT_POSTFIELDS] = (string) $request->getBody(); + // Don't duplicate the Content-Length header + $this->removeHeader('Content-Length', $conf); + $this->removeHeader('Transfer-Encoding', $conf); + } else { + $conf[CURLOPT_UPLOAD] = true; + if ($size !== null) { + $conf[CURLOPT_INFILESIZE] = $size; + $this->removeHeader('Content-Length', $conf); + } + $body = $request->getBody(); + $conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) { + return $body->read($length); + }; + } + + // If the Expect header is not present, prevent curl from adding it + if (!$request->hasHeader('Expect')) { + $conf[CURLOPT_HTTPHEADER][] = 'Expect:'; + } + + // cURL sometimes adds a content-type by default. Prevent this. + if (!$request->hasHeader('Content-Type')) { + $conf[CURLOPT_HTTPHEADER][] = 'Content-Type:'; + } + } + + private function applyHeaders(EasyHandle $easy, array &$conf) + { + foreach ($conf['_headers'] as $name => $values) { + foreach ($values as $value) { + $conf[CURLOPT_HTTPHEADER][] = "$name: $value"; + } + } + + // Remove the Accept header if one was not set + if (!$easy->request->hasHeader('Accept')) { + $conf[CURLOPT_HTTPHEADER][] = 'Accept:'; + } + } + + /** + * Remove a header from the options array. + * + * @param string $name Case-insensitive header to remove + * @param array $options Array of options to modify + */ + private function removeHeader($name, array &$options) + { + foreach (array_keys($options['_headers']) as $key) { + if (!strcasecmp($key, $name)) { + unset($options['_headers'][$key]); + return; + } + } + } + + private function applyHandlerOptions(EasyHandle $easy, array &$conf) + { + $options = $easy->options; + if (isset($options['verify'])) { + if ($options['verify'] === false) { + unset($conf[CURLOPT_CAINFO]); + $conf[CURLOPT_SSL_VERIFYHOST] = 0; + $conf[CURLOPT_SSL_VERIFYPEER] = false; + } else { + $conf[CURLOPT_SSL_VERIFYHOST] = 2; + $conf[CURLOPT_SSL_VERIFYPEER] = true; + if (is_string($options['verify'])) { + $conf[CURLOPT_CAINFO] = $options['verify']; + if (!file_exists($options['verify'])) { + throw new \InvalidArgumentException( + "SSL CA bundle not found: {$options['verify']}" + ); + } + } + } + } + + if (!empty($options['decode_content'])) { + $accept = $easy->request->getHeaderLine('Accept-Encoding'); + if ($accept) { + $conf[CURLOPT_ENCODING] = $accept; + } else { + $conf[CURLOPT_ENCODING] = ''; + // Don't let curl send the header over the wire + $conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; + } + } + + if (isset($options['sink'])) { + $sink = $options['sink']; + if (!is_string($sink)) { + $sink = \GuzzleHttp\Psr7\stream_for($sink); + } elseif (!is_dir(dirname($sink))) { + // Ensure that the directory exists before failing in curl. + throw new \RuntimeException(sprintf( + 'Directory %s does not exist for sink value of %s', + dirname($sink), + $sink + )); + } else { + $sink = new LazyOpenStream($sink, 'w+'); + } + $easy->sink = $sink; + $conf[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($sink) { + return $sink->write($write); + }; + } else { + // Use a default temp stream if no sink was set. + $conf[CURLOPT_FILE] = fopen('php://temp', 'w+'); + $easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]); + } + + if (isset($options['timeout'])) { + $conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000; + } + + if (isset($options['connect_timeout'])) { + $conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000; + } + + if (isset($options['proxy'])) { + if (!is_array($options['proxy'])) { + $conf[CURLOPT_PROXY] = $options['proxy']; + } else { + $scheme = $easy->request->getUri()->getScheme(); + if (isset($options['proxy'][$scheme])) { + $host = $easy->request->getUri()->getHost(); + if (!isset($options['proxy']['no']) || + !\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no']) + ) { + $conf[CURLOPT_PROXY] = $options['proxy'][$scheme]; + } + } + } + } + + if (isset($options['cert'])) { + $cert = $options['cert']; + if (is_array($cert)) { + $conf[CURLOPT_SSLCERTPASSWD] = $cert[1]; + $cert = $cert[0]; + } + if (!file_exists($cert)) { + throw new \InvalidArgumentException( + "SSL certificate not found: {$cert}" + ); + } + $conf[CURLOPT_SSLCERT] = $cert; + } + + if (isset($options['ssl_key'])) { + $sslKey = $options['ssl_key']; + if (is_array($sslKey)) { + $conf[CURLOPT_SSLKEYPASSWD] = $sslKey[1]; + $sslKey = $sslKey[0]; + } + if (!file_exists($sslKey)) { + throw new \InvalidArgumentException( + "SSL private key not found: {$sslKey}" + ); + } + $conf[CURLOPT_SSLKEY] = $sslKey; + } + + if (isset($options['progress'])) { + $progress = $options['progress']; + if (!is_callable($progress)) { + throw new \InvalidArgumentException( + 'progress client option must be callable' + ); + } + $conf[CURLOPT_NOPROGRESS] = false; + $conf[CURLOPT_PROGRESSFUNCTION] = function () use ($progress) { + $args = func_get_args(); + // PHP 5.5 pushed the handle onto the start of the args + if (is_resource($args[0])) { + array_shift($args); + } + call_user_func_array($progress, $args); + }; + } + + if (!empty($options['debug'])) { + $conf[CURLOPT_STDERR] = \GuzzleHttp\debug_resource($options['debug']); + $conf[CURLOPT_VERBOSE] = true; + } + } + + /** + * This function ensures that a response was set on a transaction. If one + * was not set, then the request is retried if possible. This error + * typically means you are sending a payload, curl encountered a + * "Connection died, retrying a fresh connect" error, tried to rewind the + * stream, and then encountered a "necessary data rewind wasn't possible" + * error, causing the request to be sent through curl_multi_info_read() + * without an error status. + */ + private static function retryFailedRewind( + callable $handler, + EasyHandle $easy, + array $ctx + ) { + try { + // Only rewind if the body has been read from. + $body = $easy->request->getBody(); + if ($body->tell() > 0) { + $body->rewind(); + } + } catch (\RuntimeException $e) { + $ctx['error'] = 'The connection unexpectedly failed without ' + . 'providing an error. The request would have been retried, ' + . 'but attempting to rewind the request body failed. ' + . 'Exception: ' . $e; + return self::createRejection($easy, $ctx); + } + + // Retry no more than 3 times before giving up. + if (!isset($easy->options['_curl_retries'])) { + $easy->options['_curl_retries'] = 1; + } elseif ($easy->options['_curl_retries'] == 2) { + $ctx['error'] = 'The cURL request was retried 3 times ' + . 'and did not succeed. The most likely reason for the failure ' + . 'is that cURL was unable to rewind the body of the request ' + . 'and subsequent retries resulted in the same error. Turn on ' + . 'the debug option to see what went wrong. See ' + . 'https://bugs.php.net/bug.php?id=47204 for more information.'; + return self::createRejection($easy, $ctx); + } else { + $easy->options['_curl_retries']++; + } + + return $handler($easy->request, $easy->options); + } + + private function createHeaderFn(EasyHandle $easy) + { + if (!isset($easy->options['on_headers'])) { + $onHeaders = null; + } elseif (!is_callable($easy->options['on_headers'])) { + throw new \InvalidArgumentException('on_headers must be callable'); + } else { + $onHeaders = $easy->options['on_headers']; + } + + return function ($ch, $h) use ( + $onHeaders, + $easy, + &$startingResponse + ) { + $value = trim($h); + if ($value === '') { + $startingResponse = true; + $easy->createResponse(); + if ($onHeaders) { + try { + $onHeaders($easy->response); + } catch (\Exception $e) { + // Associate the exception with the handle and trigger + // a curl header write error by returning 0. + $easy->onHeadersException = $e; + return -1; + } + } + } elseif ($startingResponse) { + $startingResponse = false; + $easy->headers = [$value]; + } else { + $easy->headers[] = $value; + } + return strlen($h); + }; + } +} diff --git a/Lib/Alisms/GuzzleHttp/Handler/CurlFactoryInterface.php b/Lib/Alisms/GuzzleHttp/Handler/CurlFactoryInterface.php new file mode 100644 index 0000000..b0fc236 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Handler/CurlFactoryInterface.php @@ -0,0 +1,27 @@ +factory = isset($options['handle_factory']) + ? $options['handle_factory'] + : new CurlFactory(3); + } + + public function __invoke(RequestInterface $request, array $options) + { + if (isset($options['delay'])) { + usleep($options['delay'] * 1000); + } + + $easy = $this->factory->create($request, $options); + curl_exec($easy->handle); + $easy->errno = curl_errno($easy->handle); + + return CurlFactory::finish($this, $easy, $this->factory); + } +} diff --git a/Lib/Alisms/GuzzleHttp/Handler/CurlMultiHandler.php b/Lib/Alisms/GuzzleHttp/Handler/CurlMultiHandler.php new file mode 100644 index 0000000..417850b --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Handler/CurlMultiHandler.php @@ -0,0 +1,197 @@ +factory = isset($options['handle_factory']) + ? $options['handle_factory'] : new CurlFactory(50); + $this->selectTimeout = isset($options['select_timeout']) + ? $options['select_timeout'] : 1; + } + + public function __get($name) + { + if ($name === '_mh') { + return $this->_mh = curl_multi_init(); + } + + throw new \BadMethodCallException(); + } + + public function __destruct() + { + if (isset($this->_mh)) { + curl_multi_close($this->_mh); + unset($this->_mh); + } + } + + public function __invoke(RequestInterface $request, array $options) + { + $easy = $this->factory->create($request, $options); + $id = (int) $easy->handle; + + $promise = new Promise( + [$this, 'execute'], + function () use ($id) { return $this->cancel($id); } + ); + + $this->addRequest(['easy' => $easy, 'deferred' => $promise]); + + return $promise; + } + + /** + * Ticks the curl event loop. + */ + public function tick() + { + // Add any delayed handles if needed. + if ($this->delays) { + $currentTime = microtime(true); + foreach ($this->delays as $id => $delay) { + if ($currentTime >= $delay) { + unset($this->delays[$id]); + curl_multi_add_handle( + $this->_mh, + $this->handles[$id]['easy']->handle + ); + } + } + } + + // Step through the task queue which may add additional requests. + P\queue()->run(); + + if ($this->active && + curl_multi_select($this->_mh, $this->selectTimeout) === -1 + ) { + // Perform a usleep if a select returns -1. + // See: https://bugs.php.net/bug.php?id=61141 + usleep(250); + } + + while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM); + + $this->processMessages(); + } + + /** + * Runs until all outstanding connections have completed. + */ + public function execute() + { + $queue = P\queue(); + + while ($this->handles || !$queue->isEmpty()) { + // If there are no transfers, then sleep for the next delay + if (!$this->active && $this->delays) { + usleep($this->timeToNext()); + } + $this->tick(); + } + } + + private function addRequest(array $entry) + { + $easy = $entry['easy']; + $id = (int) $easy->handle; + $this->handles[$id] = $entry; + if (empty($easy->options['delay'])) { + curl_multi_add_handle($this->_mh, $easy->handle); + } else { + $this->delays[$id] = microtime(true) + ($easy->options['delay'] / 1000); + } + } + + /** + * Cancels a handle from sending and removes references to it. + * + * @param int $id Handle ID to cancel and remove. + * + * @return bool True on success, false on failure. + */ + private function cancel($id) + { + // Cannot cancel if it has been processed. + if (!isset($this->handles[$id])) { + return false; + } + + $handle = $this->handles[$id]['easy']->handle; + unset($this->delays[$id], $this->handles[$id]); + curl_multi_remove_handle($this->_mh, $handle); + curl_close($handle); + + return true; + } + + private function processMessages() + { + while ($done = curl_multi_info_read($this->_mh)) { + $id = (int) $done['handle']; + curl_multi_remove_handle($this->_mh, $done['handle']); + + if (!isset($this->handles[$id])) { + // Probably was cancelled. + continue; + } + + $entry = $this->handles[$id]; + unset($this->handles[$id], $this->delays[$id]); + $entry['easy']->errno = $done['result']; + $entry['deferred']->resolve( + CurlFactory::finish( + $this, + $entry['easy'], + $this->factory + ) + ); + } + } + + private function timeToNext() + { + $currentTime = microtime(true); + $nextTime = PHP_INT_MAX; + foreach ($this->delays as $time) { + if ($time < $nextTime) { + $nextTime = $time; + } + } + + return max(0, $currentTime - $nextTime); + } +} diff --git a/Lib/Alisms/GuzzleHttp/Handler/EasyHandle.php b/Lib/Alisms/GuzzleHttp/Handler/EasyHandle.php new file mode 100644 index 0000000..c4b927e --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Handler/EasyHandle.php @@ -0,0 +1,87 @@ +headers)) { + throw new \RuntimeException('No headers have been received'); + } + + // HTTP-version SP status-code SP reason-phrase + $startLine = explode(' ', array_shift($this->headers), 3); + $headers = \GuzzleHttp\headers_from_lines($this->headers); + $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers); + + if (!empty($this->options['decode_content']) + && isset($normalizedKeys['content-encoding']) + ) { + unset($headers[$normalizedKeys['content-encoding']]); + if (isset($normalizedKeys['content-length'])) { + $bodyLength = (int) $this->sink->getSize(); + if ($bodyLength) { + $headers[$normalizedKeys['content-length']] = $bodyLength; + } else { + unset($headers[$normalizedKeys['content-length']]); + } + } + } + + // Attach a response to the easy handle with the parsed headers. + $this->response = new Response( + $startLine[1], + $headers, + $this->sink, + substr($startLine[0], 5), + isset($startLine[2]) ? (string) $startLine[2] : null + ); + } + + public function __get($name) + { + $msg = $name === 'handle' + ? 'The EasyHandle has been released' + : 'Invalid property: ' . $name; + throw new \BadMethodCallException($msg); + } +} diff --git a/Lib/Alisms/GuzzleHttp/Handler/MockHandler.php b/Lib/Alisms/GuzzleHttp/Handler/MockHandler.php new file mode 100644 index 0000000..4b1b0af --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Handler/MockHandler.php @@ -0,0 +1,163 @@ +onFulfilled = $onFulfilled; + $this->onRejected = $onRejected; + + if ($queue) { + call_user_func_array([$this, 'append'], $queue); + } + } + + public function __invoke(RequestInterface $request, array $options) + { + if (!$this->queue) { + throw new \OutOfBoundsException('Mock queue is empty'); + } + + if (isset($options['delay'])) { + usleep($options['delay'] * 1000); + } + + $this->lastRequest = $request; + $this->lastOptions = $options; + $response = array_shift($this->queue); + + if (is_callable($response)) { + $response = $response($request, $options); + } + + $response = $response instanceof \Exception + ? new RejectedPromise($response) + : \GuzzleHttp\Promise\promise_for($response); + + return $response->then( + function ($value) use ($request, $options) { + $this->invokeStats($request, $options, $value); + if ($this->onFulfilled) { + call_user_func($this->onFulfilled, $value); + } + return $value; + }, + function ($reason) use ($request, $options) { + $this->invokeStats($request, $options, null, $reason); + if ($this->onRejected) { + call_user_func($this->onRejected, $reason); + } + return new RejectedPromise($reason); + } + ); + } + + /** + * Adds one or more variadic requests, exceptions, callables, or promises + * to the queue. + */ + public function append() + { + foreach (func_get_args() as $value) { + if ($value instanceof ResponseInterface + || $value instanceof \Exception + || $value instanceof PromiseInterface + || is_callable($value) + ) { + $this->queue[] = $value; + } else { + throw new \InvalidArgumentException('Expected a response or ' + . 'exception. Found ' . \GuzzleHttp\describe_type($value)); + } + } + } + + /** + * Get the last received request. + * + * @return RequestInterface + */ + public function getLastRequest() + { + return $this->lastRequest; + } + + /** + * Get the last received request options. + * + * @return RequestInterface + */ + public function getLastOptions() + { + return $this->lastOptions; + } + + /** + * Returns the number of remaining items in the queue. + * + * @return int + */ + public function count() + { + return count($this->queue); + } + + private function invokeStats( + RequestInterface $request, + array $options, + ResponseInterface $response = null, + $reason = null + ) { + if (isset($options['on_stats'])) { + $stats = new TransferStats($request, $response, 0, $reason); + call_user_func($options['on_stats'], $stats); + } + } +} diff --git a/Lib/Alisms/GuzzleHttp/Handler/Proxy.php b/Lib/Alisms/GuzzleHttp/Handler/Proxy.php new file mode 100644 index 0000000..9bd76d2 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Handler/Proxy.php @@ -0,0 +1,54 @@ +withoutHeader('Expect'); + + // Append a content-length header if body size is zero to match + // cURL's behavior. + if (0 === $request->getBody()->getSize()) { + $request = $request->withHeader('Content-Length', 0); + } + + return $this->createResponse( + $request, + $options, + $this->createStream($request, $options), + $startTime + ); + } catch (\InvalidArgumentException $e) { + throw $e; + } catch (\Exception $e) { + // Determine if the error was a networking error. + $message = $e->getMessage(); + // This list can probably get more comprehensive. + if (strpos($message, 'getaddrinfo') // DNS lookup failed + || strpos($message, 'Connection refused') + || strpos($message, "couldn't connect to host") // error on HHVM + ) { + $e = new ConnectException($e->getMessage(), $request, $e); + } + $e = RequestException::wrapException($request, $e); + $this->invokeStats($options, $request, $startTime, null, $e); + + return new RejectedPromise($e); + } + } + + private function invokeStats( + array $options, + RequestInterface $request, + $startTime, + ResponseInterface $response = null, + $error = null + ) { + if (isset($options['on_stats'])) { + $stats = new TransferStats( + $request, + $response, + microtime(true) - $startTime, + $error, + [] + ); + call_user_func($options['on_stats'], $stats); + } + } + + private function createResponse( + RequestInterface $request, + array $options, + $stream, + $startTime + ) { + $hdrs = $this->lastHeaders; + $this->lastHeaders = []; + $parts = explode(' ', array_shift($hdrs), 3); + $ver = explode('/', $parts[0])[1]; + $status = $parts[1]; + $reason = isset($parts[2]) ? $parts[2] : null; + $headers = \GuzzleHttp\headers_from_lines($hdrs); + list ($stream, $headers) = $this->checkDecode($options, $headers, $stream); + $stream = Psr7\stream_for($stream); + $sink = $this->createSink($stream, $options); + $response = new Psr7\Response($status, $headers, $sink, $ver, $reason); + + if (isset($options['on_headers'])) { + try { + $options['on_headers']($response); + } catch (\Exception $e) { + $msg = 'An error was encountered during the on_headers event'; + $ex = new RequestException($msg, $request, $response, $e); + return new RejectedPromise($ex); + } + } + + if ($sink !== $stream) { + $this->drain($stream, $sink); + } + + $this->invokeStats($options, $request, $startTime, $response, null); + + return new FulfilledPromise($response); + } + + private function createSink(StreamInterface $stream, array $options) + { + if (!empty($options['stream'])) { + return $stream; + } + + $sink = isset($options['sink']) + ? $options['sink'] + : fopen('php://temp', 'r+'); + + return is_string($sink) + ? new Psr7\Stream(Psr7\try_fopen($sink, 'r+')) + : Psr7\stream_for($sink); + } + + private function checkDecode(array $options, array $headers, $stream) + { + // Automatically decode responses when instructed. + if (!empty($options['decode_content'])) { + $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers); + if (isset($normalizedKeys['content-encoding'])) { + $encoding = $headers[$normalizedKeys['content-encoding']]; + if ($encoding[0] == 'gzip' || $encoding[0] == 'deflate') { + $stream = new Psr7\InflateStream( + Psr7\stream_for($stream) + ); + // Remove content-encoding header + unset($headers[$normalizedKeys['content-encoding']]); + // Fix content-length header + if (isset($normalizedKeys['content-length'])) { + $length = (int) $stream->getSize(); + if ($length == 0) { + unset($headers[$normalizedKeys['content-length']]); + } else { + $headers[$normalizedKeys['content-length']] = [$length]; + } + } + } + } + } + + return [$stream, $headers]; + } + + /** + * Drains the source stream into the "sink" client option. + * + * @param StreamInterface $source + * @param StreamInterface $sink + * + * @return StreamInterface + * @throws \RuntimeException when the sink option is invalid. + */ + private function drain(StreamInterface $source, StreamInterface $sink) + { + Psr7\copy_to_stream($source, $sink); + $sink->seek(0); + $source->close(); + + return $sink; + } + + /** + * Create a resource and check to ensure it was created successfully + * + * @param callable $callback Callable that returns stream resource + * + * @return resource + * @throws \RuntimeException on error + */ + private function createResource(callable $callback) + { + $errors = null; + set_error_handler(function ($_, $msg, $file, $line) use (&$errors) { + $errors[] = [ + 'message' => $msg, + 'file' => $file, + 'line' => $line + ]; + return true; + }); + + $resource = $callback(); + restore_error_handler(); + + if (!$resource) { + $message = 'Error creating resource: '; + foreach ($errors as $err) { + foreach ($err as $key => $value) { + $message .= "[$key] $value" . PHP_EOL; + } + } + throw new \RuntimeException(trim($message)); + } + + return $resource; + } + + private function createStream(RequestInterface $request, array $options) + { + static $methods; + if (!$methods) { + $methods = array_flip(get_class_methods(__CLASS__)); + } + + // HTTP/1.1 streams using the PHP stream wrapper require a + // Connection: close header + if ($request->getProtocolVersion() == '1.1' + && !$request->hasHeader('Connection') + ) { + $request = $request->withHeader('Connection', 'close'); + } + + // Ensure SSL is verified by default + if (!isset($options['verify'])) { + $options['verify'] = true; + } + + $params = []; + $context = $this->getDefaultContext($request, $options); + + if (isset($options['on_headers']) && !is_callable($options['on_headers'])) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + + if (!empty($options)) { + foreach ($options as $key => $value) { + $method = "add_{$key}"; + if (isset($methods[$method])) { + $this->{$method}($request, $context, $value, $params); + } + } + } + + if (isset($options['stream_context'])) { + if (!is_array($options['stream_context'])) { + throw new \InvalidArgumentException('stream_context must be an array'); + } + $context = array_replace_recursive( + $context, + $options['stream_context'] + ); + } + + $context = $this->createResource( + function () use ($context, $params) { + return stream_context_create($context, $params); + } + ); + + return $this->createResource( + function () use ($request, &$http_response_header, $context) { + $resource = fopen($request->getUri(), 'r', null, $context); + $this->lastHeaders = $http_response_header; + return $resource; + } + ); + } + + private function getDefaultContext(RequestInterface $request) + { + $headers = ''; + foreach ($request->getHeaders() as $name => $value) { + foreach ($value as $val) { + $headers .= "$name: $val\r\n"; + } + } + + $context = [ + 'http' => [ + 'method' => $request->getMethod(), + 'header' => $headers, + 'protocol_version' => $request->getProtocolVersion(), + 'ignore_errors' => true, + 'follow_location' => 0, + ], + ]; + + $body = (string) $request->getBody(); + + if (!empty($body)) { + $context['http']['content'] = $body; + // Prevent the HTTP handler from adding a Content-Type header. + if (!$request->hasHeader('Content-Type')) { + $context['http']['header'] .= "Content-Type:\r\n"; + } + } + + $context['http']['header'] = rtrim($context['http']['header']); + + return $context; + } + + private function add_proxy(RequestInterface $request, &$options, $value, &$params) + { + if (!is_array($value)) { + $options['http']['proxy'] = $value; + } else { + $scheme = $request->getUri()->getScheme(); + if (isset($value[$scheme])) { + if (!isset($value['no']) + || !\GuzzleHttp\is_host_in_noproxy( + $request->getUri()->getHost(), + $value['no'] + ) + ) { + $options['http']['proxy'] = $value[$scheme]; + } + } + } + } + + private function add_timeout(RequestInterface $request, &$options, $value, &$params) + { + $options['http']['timeout'] = $value; + } + + private function add_verify(RequestInterface $request, &$options, $value, &$params) + { + if ($value === true) { + // PHP 5.6 or greater will find the system cert by default. When + // < 5.6, use the Guzzle bundled cacert. + if (PHP_VERSION_ID < 50600) { + $options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle(); + } + } elseif (is_string($value)) { + $options['ssl']['cafile'] = $value; + if (!file_exists($value)) { + throw new \RuntimeException("SSL CA bundle not found: $value"); + } + } elseif ($value === false) { + $options['ssl']['verify_peer'] = false; + return; + } else { + throw new \InvalidArgumentException('Invalid verify request option'); + } + + $options['ssl']['verify_peer'] = true; + $options['ssl']['allow_self_signed'] = false; + } + + private function add_cert(RequestInterface $request, &$options, $value, &$params) + { + if (is_array($value)) { + $options['ssl']['passphrase'] = $value[1]; + $value = $value[0]; + } + + if (!file_exists($value)) { + throw new \RuntimeException("SSL certificate not found: {$value}"); + } + + $options['ssl']['local_cert'] = $value; + } + + private function add_progress(RequestInterface $request, &$options, $value, &$params) + { + $this->addNotification( + $params, + function ($code, $a, $b, $c, $transferred, $total) use ($value) { + if ($code == STREAM_NOTIFY_PROGRESS) { + $value($total, $transferred, null, null); + } + } + ); + } + + private function add_debug(RequestInterface $request, &$options, $value, &$params) + { + if ($value === false) { + return; + } + + static $map = [ + STREAM_NOTIFY_CONNECT => 'CONNECT', + STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', + STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT', + STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS', + STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS', + STREAM_NOTIFY_REDIRECTED => 'REDIRECTED', + STREAM_NOTIFY_PROGRESS => 'PROGRESS', + STREAM_NOTIFY_FAILURE => 'FAILURE', + STREAM_NOTIFY_COMPLETED => 'COMPLETED', + STREAM_NOTIFY_RESOLVE => 'RESOLVE', + ]; + static $args = ['severity', 'message', 'message_code', + 'bytes_transferred', 'bytes_max']; + + $value = \GuzzleHttp\debug_resource($value); + $ident = $request->getMethod() . ' ' . $request->getUri(); + $this->addNotification( + $params, + function () use ($ident, $value, $map, $args) { + $passed = func_get_args(); + $code = array_shift($passed); + fprintf($value, '<%s> [%s] ', $ident, $map[$code]); + foreach (array_filter($passed) as $i => $v) { + fwrite($value, $args[$i] . ': "' . $v . '" '); + } + fwrite($value, "\n"); + } + ); + } + + private function addNotification(array &$params, callable $notify) + { + // Wrap the existing function if needed. + if (!isset($params['notification'])) { + $params['notification'] = $notify; + } else { + $params['notification'] = $this->callArray([ + $params['notification'], + $notify + ]); + } + } + + private function callArray(array $functions) + { + return function () use ($functions) { + $args = func_get_args(); + foreach ($functions as $fn) { + call_user_func_array($fn, $args); + } + }; + } +} diff --git a/Lib/Alisms/GuzzleHttp/HandlerStack.php b/Lib/Alisms/GuzzleHttp/HandlerStack.php new file mode 100644 index 0000000..f851849 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/HandlerStack.php @@ -0,0 +1,272 @@ +push(Middleware::httpErrors(), 'http_errors'); + $stack->push(Middleware::redirect(), 'allow_redirects'); + $stack->push(Middleware::cookies(), 'cookies'); + $stack->push(Middleware::prepareBody(), 'prepare_body'); + + return $stack; + } + + /** + * @param callable $handler Underlying HTTP handler. + */ + public function __construct(callable $handler = null) + { + $this->handler = $handler; + } + + /** + * Invokes the handler stack as a composed handler + * + * @param RequestInterface $request + * @param array $options + */ + public function __invoke(RequestInterface $request, array $options) + { + if (!$this->cached) { + $this->cached = $this->resolve(); + } + + $handler = $this->cached; + return $handler($request, $options); + } + + /** + * Dumps a string representation of the stack. + * + * @return string + */ + public function __toString() + { + $depth = 0; + $stack = []; + if ($this->handler) { + $stack[] = "0) Handler: " . $this->debugCallable($this->handler); + } + + $result = ''; + foreach (array_reverse($this->stack) as $tuple) { + $depth++; + $str = "{$depth}) Name: '{$tuple[1]}', "; + $str .= "Function: " . $this->debugCallable($tuple[0]); + $result = "> {$str}\n{$result}"; + $stack[] = $str; + } + + foreach (array_keys($stack) as $k) { + $result .= "< {$stack[$k]}\n"; + } + + return $result; + } + + /** + * Set the HTTP handler that actually returns a promise. + * + * @param callable $handler Accepts a request and array of options and + * returns a Promise. + */ + public function setHandler(callable $handler) + { + $this->handler = $handler; + $this->cached = null; + } + + /** + * Returns true if the builder has a handler. + * + * @return bool + */ + public function hasHandler() + { + return (bool) $this->handler; + } + + /** + * Unshift a middleware to the bottom of the stack. + * + * @param callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function unshift(callable $middleware, $name = null) + { + array_unshift($this->stack, [$middleware, $name]); + $this->cached = null; + } + + /** + * Push a middleware to the top of the stack. + * + * @param callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function push(callable $middleware, $name = '') + { + $this->stack[] = [$middleware, $name]; + $this->cached = null; + } + + /** + * Add a middleware before another middleware by name. + * + * @param string $findName Middleware to find + * @param callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function before($findName, callable $middleware, $withName = '') + { + $this->splice($findName, $withName, $middleware, true); + } + + /** + * Add a middleware after another middleware by name. + * + * @param string $findName Middleware to find + * @param callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function after($findName, callable $middleware, $withName = '') + { + $this->splice($findName, $withName, $middleware, false); + } + + /** + * Remove a middleware by instance or name from the stack. + * + * @param callable|string $remove Middleware to remove by instance or name. + */ + public function remove($remove) + { + $this->cached = null; + $idx = is_callable($remove) ? 0 : 1; + $this->stack = array_values(array_filter( + $this->stack, + function ($tuple) use ($idx, $remove) { + return $tuple[$idx] !== $remove; + } + )); + } + + /** + * Compose the middleware and handler into a single callable function. + * + * @return callable + */ + public function resolve() + { + if (!($prev = $this->handler)) { + throw new \LogicException('No handler has been specified'); + } + + foreach (array_reverse($this->stack) as $fn) { + $prev = $fn[0]($prev); + } + + return $prev; + } + + /** + * @param $name + * @return int + */ + private function findByName($name) + { + foreach ($this->stack as $k => $v) { + if ($v[1] === $name) { + return $k; + } + } + + throw new \InvalidArgumentException("Middleware not found: $name"); + } + + /** + * Splices a function into the middleware list at a specific position. + * + * @param $findName + * @param $withName + * @param callable $middleware + * @param $before + */ + private function splice($findName, $withName, callable $middleware, $before) + { + $this->cached = null; + $idx = $this->findByName($findName); + $tuple = [$middleware, $withName]; + + if ($before) { + if ($idx === 0) { + array_unshift($this->stack, $tuple); + } else { + $replacement = [$tuple, $this->stack[$idx]]; + array_splice($this->stack, $idx, 1, $replacement); + } + } elseif ($idx === count($this->stack) - 1) { + $this->stack[] = $tuple; + } else { + $replacement = [$this->stack[$idx], $tuple]; + array_splice($this->stack, $idx, 1, $replacement); + } + } + + /** + * Provides a debug string for a given callable. + * + * @param array|callable $fn Function to write as a string. + * + * @return string + */ + private function debugCallable($fn) + { + if (is_string($fn)) { + return "callable({$fn})"; + } + + if (is_array($fn)) { + return is_string($fn[0]) + ? "callable({$fn[0]}::{$fn[1]})" + : "callable(['" . get_class($fn[0]) . "', '{$fn[1]}'])"; + } + + return 'callable(' . spl_object_hash($fn) . ')'; + } +} diff --git a/Lib/Alisms/GuzzleHttp/MessageFormatter.php b/Lib/Alisms/GuzzleHttp/MessageFormatter.php new file mode 100644 index 0000000..6b090a9 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/MessageFormatter.php @@ -0,0 +1,182 @@ +>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}"; + const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}'; + + /** @var string Template used to format log messages */ + private $template; + + /** + * @param string $template Log message template + */ + public function __construct($template = self::CLF) + { + $this->template = $template ?: self::CLF; + } + + /** + * Returns a formatted message string. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface $response Response that was received + * @param \Exception $error Exception that was received + * + * @return string + */ + public function format( + RequestInterface $request, + ResponseInterface $response = null, + \Exception $error = null + ) { + $cache = []; + + return preg_replace_callback( + '/{\s*([A-Za-z_\-\.0-9]+)\s*}/', + function (array $matches) use ($request, $response, $error, &$cache) { + + if (isset($cache[$matches[1]])) { + return $cache[$matches[1]]; + } + + $result = ''; + switch ($matches[1]) { + case 'request': + $result = Psr7\str($request); + break; + case 'response': + $result = $response ? Psr7\str($response) : ''; + break; + case 'req_headers': + $result = trim($request->getMethod() + . ' ' . $request->getRequestTarget()) + . ' HTTP/' . $request->getProtocolVersion() . "\r\n" + . $this->headers($request); + break; + case 'res_headers': + $result = $response ? + sprintf( + 'HTTP/%s %d %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ) . "\r\n" . $this->headers($response) + : 'NULL'; + break; + case 'req_body': + $result = $request->getBody(); + break; + case 'res_body': + $result = $response ? $response->getBody() : 'NULL'; + break; + case 'ts': + case 'date_iso_8601': + $result = gmdate('c'); + break; + case 'date_common_log': + $result = date('d/M/Y:H:i:s O'); + break; + case 'method': + $result = $request->getMethod(); + break; + case 'version': + $result = $request->getProtocolVersion(); + break; + case 'uri': + case 'url': + $result = $request->getUri(); + break; + case 'target': + $result = $request->getRequestTarget(); + break; + case 'req_version': + $result = $request->getProtocolVersion(); + break; + case 'res_version': + $result = $response + ? $response->getProtocolVersion() + : 'NULL'; + break; + case 'host': + $result = $request->getHeaderLine('Host'); + break; + case 'hostname': + $result = gethostname(); + break; + case 'code': + $result = $response ? $response->getStatusCode() : 'NULL'; + break; + case 'phrase': + $result = $response ? $response->getReasonPhrase() : 'NULL'; + break; + case 'error': + $result = $error ? $error->getMessage() : 'NULL'; + break; + default: + // handle prefixed dynamic headers + if (strpos($matches[1], 'req_header_') === 0) { + $result = $request->getHeaderLine(substr($matches[1], 11)); + } elseif (strpos($matches[1], 'res_header_') === 0) { + $result = $response + ? $response->getHeaderLine(substr($matches[1], 11)) + : 'NULL'; + } + } + + $cache[$matches[1]] = $result; + return $result; + }, + $this->template + ); + } + + private function headers(MessageInterface $message) + { + $result = ''; + foreach ($message->getHeaders() as $name => $values) { + $result .= $name . ': ' . implode(', ', $values) . "\r\n"; + } + + return trim($result); + } +} diff --git a/Lib/Alisms/GuzzleHttp/Middleware.php b/Lib/Alisms/GuzzleHttp/Middleware.php new file mode 100644 index 0000000..2f165f3 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Middleware.php @@ -0,0 +1,253 @@ +withCookieHeader($request); + return $handler($request, $options) + ->then(function ($response) use ($cookieJar, $request) { + $cookieJar->extractCookies($request, $response); + return $response; + } + ); + }; + }; + } + + /** + * Middleware that throws exceptions for 4xx or 5xx responses when the + * "http_error" request option is set to true. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function httpErrors() + { + return function (callable $handler) { + return function ($request, array $options) use ($handler) { + if (empty($options['http_errors'])) { + return $handler($request, $options); + } + return $handler($request, $options)->then( + function (ResponseInterface $response) use ($request, $handler) { + $code = $response->getStatusCode(); + if ($code < 400) { + return $response; + } + throw $code > 499 + ? new ServerException("Server error: $code", $request, $response) + : new ClientException("Client error: $code", $request, $response); + } + ); + }; + }; + } + + /** + * Middleware that pushes history data to an ArrayAccess container. + * + * @param array $container Container to hold the history (by reference). + * + * @return callable Returns a function that accepts the next handler. + */ + public static function history(array &$container) + { + return function (callable $handler) use (&$container) { + return function ($request, array $options) use ($handler, &$container) { + return $handler($request, $options)->then( + function ($value) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => $value, + 'error' => null, + 'options' => $options + ]; + return $value; + }, + function ($reason) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => null, + 'error' => $reason, + 'options' => $options + ]; + return new RejectedPromise($reason); + } + ); + }; + }; + } + + /** + * Middleware that invokes a callback before and after sending a request. + * + * The provided listener cannot modify or alter the response. It simply + * "taps" into the chain to be notified before returning the promise. The + * before listener accepts a request and options array, and the after + * listener accepts a request, options array, and response promise. + * + * @param callable $before Function to invoke before forwarding the request. + * @param callable $after Function invoked after forwarding. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function tap(callable $before = null, callable $after = null) + { + return function (callable $handler) use ($before, $after) { + return function ($request, array $options) use ($handler, $before, $after) { + if ($before) { + $before($request, $options); + } + $response = $handler($request, $options); + if ($after) { + $after($request, $options, $response); + } + return $response; + }; + }; + } + + /** + * Middleware that handles request redirects. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function redirect() + { + return function (callable $handler) { + return new RedirectMiddleware($handler); + }; + } + + /** + * Middleware that retries requests based on the boolean result of + * invoking the provided "decider" function. + * + * If no delay function is provided, a simple implementation of exponential + * backoff will be utilized. + * + * @param callable $decider Function that accepts the number of retries, + * a request, [response], and [exception] and + * returns true if the request is to be retried. + * @param callable $delay Function that accepts the number of retries and + * returns the number of milliseconds to delay. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function retry(callable $decider, callable $delay = null) + { + return function (callable $handler) use ($decider, $delay) { + return new RetryMiddleware($decider, $handler, $delay); + }; + } + + /** + * Middleware that logs requests, responses, and errors using a message + * formatter. + * + * @param LoggerInterface $logger Logs messages. + * @param MessageFormatter $formatter Formatter used to create message strings. + * @param string $logLevel Level at which to log requests. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = LogLevel::INFO) + { + return function (callable $handler) use ($logger, $formatter, $logLevel) { + return function ($request, array $options) use ($handler, $logger, $formatter, $logLevel) { + return $handler($request, $options)->then( + function ($response) use ($logger, $request, $formatter, $logLevel) { + $message = $formatter->format($request, $response); + $logger->log($logLevel, $message); + return $response; + }, + function ($reason) use ($logger, $request, $formatter) { + $response = $reason instanceof RequestException + ? $reason->getResponse() + : null; + $message = $formatter->format($request, $response, $reason); + $logger->notice($message); + return \GuzzleHttp\Promise\rejection_for($reason); + } + ); + }; + }; + } + + /** + * This middleware adds a default content-type if possible, a default + * content-length or transfer-encoding header, and the expect header. + * + * @return callable + */ + public static function prepareBody() + { + return function (callable $handler) { + return new PrepareBodyMiddleware($handler); + }; + } + + /** + * Middleware that applies a map function to the request before passing to + * the next handler. + * + * @param callable $fn Function that accepts a RequestInterface and returns + * a RequestInterface. + * @return callable + */ + public static function mapRequest(callable $fn) + { + return function (callable $handler) use ($fn) { + return function ($request, array $options) use ($handler, $fn) { + return $handler($fn($request), $options); + }; + }; + } + + /** + * Middleware that applies a map function to the resolved promise's + * response. + * + * @param callable $fn Function that accepts a ResponseInterface and + * returns a ResponseInterface. + * @return callable + */ + public static function mapResponse(callable $fn) + { + return function (callable $handler) use ($fn) { + return function ($request, array $options) use ($handler, $fn) { + return $handler($request, $options)->then($fn); + }; + }; + } +} diff --git a/Lib/Alisms/GuzzleHttp/Pool.php b/Lib/Alisms/GuzzleHttp/Pool.php new file mode 100644 index 0000000..bc41d6e --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Pool.php @@ -0,0 +1,123 @@ +sendAsync($rfn, $opts); + } elseif (is_callable($rfn)) { + yield $rfn($opts); + } else { + throw new \InvalidArgumentException('Each value yielded by ' + . 'the iterator must be a Psr7\Http\Message\RequestInterface ' + . 'or a callable that returns a promise that fulfills ' + . 'with a Psr7\Message\Http\ResponseInterface object.'); + } + } + }; + + $this->each = new EachPromise($requests(), $config); + } + + public function promise() + { + return $this->each->promise(); + } + + /** + * Sends multiple requests concurrently and returns an array of responses + * and exceptions that uses the same ordering as the provided requests. + * + * IMPORTANT: This method keeps every request and response in memory, and + * as such, is NOT recommended when sending a large number or an + * indeterminate number of requests concurrently. + * + * @param ClientInterface $client Client used to send the requests + * @param array|\Iterator $requests Requests to send concurrently. + * @param array $options Passes through the options available in + * {@see GuzzleHttp\Pool::__construct} + * + * @return array Returns an array containing the response or an exception + * in the same order that the requests were sent. + * @throws \InvalidArgumentException if the event format is incorrect. + */ + public static function batch( + ClientInterface $client, + $requests, + array $options = [] + ) { + $res = []; + self::cmpCallback($options, 'fulfilled', $res); + self::cmpCallback($options, 'rejected', $res); + $pool = new static($client, $requests, $options); + $pool->promise()->wait(); + ksort($res); + + return $res; + } + + private static function cmpCallback(array &$options, $name, array &$results) + { + if (!isset($options[$name])) { + $options[$name] = function ($v, $k) use (&$results) { + $results[$k] = $v; + }; + } else { + $currentFn = $options[$name]; + $options[$name] = function ($v, $k) use (&$results, $currentFn) { + $currentFn($v, $k); + $results[$k] = $v; + }; + } + } +} diff --git a/Lib/Alisms/GuzzleHttp/PrepareBodyMiddleware.php b/Lib/Alisms/GuzzleHttp/PrepareBodyMiddleware.php new file mode 100644 index 0000000..e6d176b --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/PrepareBodyMiddleware.php @@ -0,0 +1,112 @@ + true, 'HEAD' => true]; + + /** + * @param callable $nextHandler Next handler to invoke. + */ + public function __construct(callable $nextHandler) + { + $this->nextHandler = $nextHandler; + } + + /** + * @param RequestInterface $request + * @param array $options + * + * @return PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + $fn = $this->nextHandler; + + // Don't do anything if the request has no body. + if (isset(self::$skipMethods[$request->getMethod()]) + || $request->getBody()->getSize() === 0 + ) { + return $fn($request, $options); + } + + $modify = []; + + // Add a default content-type if possible. + if (!$request->hasHeader('Content-Type')) { + if ($uri = $request->getBody()->getMetadata('uri')) { + if ($type = Psr7\mimetype_from_filename($uri)) { + $modify['set_headers']['Content-Type'] = $type; + } + } + } + + // Add a default content-length or transfer-encoding header. + if (!isset(self::$skipMethods[$request->getMethod()]) + && !$request->hasHeader('Content-Length') + && !$request->hasHeader('Transfer-Encoding') + ) { + $size = $request->getBody()->getSize(); + if ($size !== null) { + $modify['set_headers']['Content-Length'] = $size; + } else { + $modify['set_headers']['Transfer-Encoding'] = 'chunked'; + } + } + + // Add the expect header if needed. + $this->addExpectHeader($request, $options, $modify); + + return $fn(Psr7\modify_request($request, $modify), $options); + } + + private function addExpectHeader( + RequestInterface $request, + array $options, + array &$modify + ) { + // Determine if the Expect header should be used + if ($request->hasHeader('Expect')) { + return; + } + + $expect = isset($options['expect']) ? $options['expect'] : null; + + // Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0 + if ($expect === false || $request->getProtocolVersion() < 1.1) { + return; + } + + // The expect header is unconditionally enabled + if ($expect === true) { + $modify['set_headers']['Expect'] = '100-Continue'; + return; + } + + // By default, send the expect header when the payload is > 1mb + if ($expect === null) { + $expect = 1048576; + } + + // Always add if the body cannot be rewound, the size cannot be + // determined, or the size is greater than the cutoff threshold + $body = $request->getBody(); + $size = $body->getSize(); + + if ($size === null || $size >= (int) $expect || !$body->isSeekable()) { + $modify['set_headers']['Expect'] = '100-Continue'; + } + } +} diff --git a/Lib/Alisms/GuzzleHttp/Promise/AggregateException.php b/Lib/Alisms/GuzzleHttp/Promise/AggregateException.php new file mode 100644 index 0000000..6a5690c --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Promise/AggregateException.php @@ -0,0 +1,16 @@ +iterable = iter_for($iterable); + + if (isset($config['concurrency'])) { + $this->concurrency = $config['concurrency']; + } + + if (isset($config['fulfilled'])) { + $this->onFulfilled = $config['fulfilled']; + } + + if (isset($config['rejected'])) { + $this->onRejected = $config['rejected']; + } + } + + public function promise() + { + if ($this->aggregate) { + return $this->aggregate; + } + + try { + $this->createPromise(); + $this->iterable->rewind(); + $this->refillPending(); + } catch (\Exception $e) { + $this->aggregate->reject($e); + } + + return $this->aggregate; + } + + private function createPromise() + { + $this->aggregate = new Promise(function () { + reset($this->pending); + // Consume a potentially fluctuating list of promises while + // ensuring that indexes are maintained (precluding array_shift). + while ($promise = current($this->pending)) { + next($this->pending); + $promise->wait(); + if ($this->aggregate->getState() !== PromiseInterface::PENDING) { + return; + } + } + }); + + // Clear the references when the promise is resolved. + $clearFn = function () { + $this->iterable = $this->concurrency = $this->pending = null; + $this->onFulfilled = $this->onRejected = null; + }; + + $this->aggregate->then($clearFn, $clearFn); + } + + private function refillPending() + { + if (!$this->concurrency) { + // Add all pending promises. + while ($this->addPending() && $this->advanceIterator()); + return; + } + + // Add only up to N pending promises. + $concurrency = is_callable($this->concurrency) + ? call_user_func($this->concurrency, count($this->pending)) + : $this->concurrency; + $concurrency = max($concurrency - count($this->pending), 0); + // Concurrency may be set to 0 to disallow new promises. + if (!$concurrency) { + return; + } + // Add the first pending promise. + $this->addPending(); + // Note this is special handling for concurrency=1 so that we do + // not advance the iterator after adding the first promise. This + // helps work around issues with generators that might not have the + // next value to yield until promise callbacks are called. + while (--$concurrency + && $this->advanceIterator() + && $this->addPending()); + } + + private function addPending() + { + if (!$this->iterable || !$this->iterable->valid()) { + return false; + } + + $promise = promise_for($this->iterable->current()); + $idx = $this->iterable->key(); + + $this->pending[$idx] = $promise->then( + function ($value) use ($idx) { + if ($this->onFulfilled) { + call_user_func( + $this->onFulfilled, $value, $idx, $this->aggregate + ); + } + $this->step($idx); + }, + function ($reason) use ($idx) { + if ($this->onRejected) { + call_user_func( + $this->onRejected, $reason, $idx, $this->aggregate + ); + } + $this->step($idx); + } + ); + + return true; + } + + private function advanceIterator() + { + try { + $this->iterable->next(); + return true; + } catch (\Exception $e) { + $this->aggregate->reject($e); + return false; + } + } + + private function step($idx) + { + // If the promise was already resolved, then ignore this step. + if ($this->aggregate->getState() !== PromiseInterface::PENDING) { + return; + } + + unset($this->pending[$idx]); + $this->advanceIterator(); + + if (!$this->checkIfFinished()) { + // Add more pending promises if possible. + $this->refillPending(); + } + } + + private function checkIfFinished() + { + if (!$this->pending && !$this->iterable->valid()) { + // Resolve the promise if there's nothing left to do. + $this->aggregate->resolve(null); + return true; + } + + return false; + } +} diff --git a/Lib/Alisms/GuzzleHttp/Promise/FulfilledPromise.php b/Lib/Alisms/GuzzleHttp/Promise/FulfilledPromise.php new file mode 100644 index 0000000..5596296 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Promise/FulfilledPromise.php @@ -0,0 +1,80 @@ +value = $value; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // Return itself if there is no onFulfilled function. + if (!$onFulfilled) { + return $this; + } + + $queue = queue(); + $p = new Promise([$queue, 'run']); + $value = $this->value; + $queue->add(static function () use ($p, $value, $onFulfilled) { + if ($p->getState() === self::PENDING) { + try { + $p->resolve($onFulfilled($value)); + } catch (\Exception $e) { + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + return $unwrap ? $this->value : null; + } + + public function getState() + { + return self::FULFILLED; + } + + public function resolve($value) + { + if ($value !== $this->value) { + throw new \LogicException("Cannot resolve a fulfilled promise"); + } + } + + public function reject($reason) + { + throw new \LogicException("Cannot reject a fulfilled promise"); + } + + public function cancel() + { + // pass + } +} diff --git a/Lib/Alisms/GuzzleHttp/Promise/Promise.php b/Lib/Alisms/GuzzleHttp/Promise/Promise.php new file mode 100644 index 0000000..c2cf969 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Promise/Promise.php @@ -0,0 +1,268 @@ +waitFn = $waitFn; + $this->cancelFn = $cancelFn; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + if ($this->state === self::PENDING) { + $p = new Promise(null, [$this, 'cancel']); + $this->handlers[] = [$p, $onFulfilled, $onRejected]; + $p->waitList = $this->waitList; + $p->waitList[] = $this; + return $p; + } + + // Return a fulfilled promise and immediately invoke any callbacks. + if ($this->state === self::FULFILLED) { + return $onFulfilled + ? promise_for($this->result)->then($onFulfilled) + : promise_for($this->result); + } + + // It's either cancelled or rejected, so return a rejected promise + // and immediately invoke any callbacks. + $rejection = rejection_for($this->result); + return $onRejected ? $rejection->then(null, $onRejected) : $rejection; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true) + { + $this->waitIfPending(); + + if (!$unwrap) { + return null; + } + + if ($this->result instanceof PromiseInterface) { + return $this->result->wait($unwrap); + } elseif ($this->state === self::FULFILLED) { + return $this->result; + } else { + // It's rejected so "unwrap" and throw an exception. + throw exception_for($this->result); + } + } + + public function getState() + { + return $this->state; + } + + public function cancel() + { + if ($this->state !== self::PENDING) { + return; + } + + $this->waitFn = $this->waitList = null; + + if ($this->cancelFn) { + $fn = $this->cancelFn; + $this->cancelFn = null; + try { + $fn(); + } catch (\Exception $e) { + $this->reject($e); + } + } + + // Reject the promise only if it wasn't rejected in a then callback. + if ($this->state === self::PENDING) { + $this->reject(new CancellationException('Promise has been cancelled')); + } + } + + public function resolve($value) + { + $this->settle(self::FULFILLED, $value); + } + + public function reject($reason) + { + $this->settle(self::REJECTED, $reason); + } + + private function settle($state, $value) + { + if ($this->state !== self::PENDING) { + // Ignore calls with the same resolution. + if ($state === $this->state && $value === $this->result) { + return; + } + throw $this->state === $state + ? new \LogicException("The promise is already {$state}.") + : new \LogicException("Cannot change a {$this->state} promise to {$state}"); + } + + if ($value === $this) { + throw new \LogicException('Cannot fulfill or reject a promise with itself'); + } + + // Clear out the state of the promise but stash the handlers. + $this->state = $state; + $this->result = $value; + $handlers = $this->handlers; + $this->handlers = null; + $this->waitList = $this->waitFn = null; + $this->cancelFn = null; + + if (!$handlers) { + return; + } + + // If the value was not a settled promise or a thenable, then resolve + // it in the task queue using the correct ID. + if (!method_exists($value, 'then')) { + $id = $state === self::FULFILLED ? 1 : 2; + // It's a success, so resolve the handlers in the queue. + queue()->add(static function () use ($id, $value, $handlers) { + foreach ($handlers as $handler) { + self::callHandler($id, $value, $handler); + } + }); + } elseif ($value instanceof Promise + && $value->getState() === self::PENDING + ) { + // We can just merge our handlers onto the next promise. + $value->handlers = array_merge($value->handlers, $handlers); + } else { + // Resolve the handlers when the forwarded promise is resolved. + $value->then( + static function ($value) use ($handlers) { + foreach ($handlers as $handler) { + self::callHandler(1, $value, $handler); + } + }, + static function ($reason) use ($handlers) { + foreach ($handlers as $handler) { + self::callHandler(2, $reason, $handler); + } + } + ); + } + } + + /** + * Call a stack of handlers using a specific callback index and value. + * + * @param int $index 1 (resolve) or 2 (reject). + * @param mixed $value Value to pass to the callback. + * @param array $handler Array of handler data (promise and callbacks). + * + * @return array Returns the next group to resolve. + */ + private static function callHandler($index, $value, array $handler) + { + /** @var PromiseInterface $promise */ + $promise = $handler[0]; + + // The promise may have been cancelled or resolved before placing + // this thunk in the queue. + if ($promise->getState() !== self::PENDING) { + return; + } + + try { + if (isset($handler[$index])) { + $promise->resolve($handler[$index]($value)); + } elseif ($index === 1) { + // Forward resolution values as-is. + $promise->resolve($value); + } else { + // Forward rejections down the chain. + $promise->reject($value); + } + } catch (\Exception $reason) { + $promise->reject($reason); + } + } + + private function waitIfPending() + { + if ($this->state !== self::PENDING) { + return; + } elseif ($this->waitFn) { + $this->invokeWaitFn(); + } elseif ($this->waitList) { + $this->invokeWaitList(); + } else { + // If there's not wait function, then reject the promise. + $this->reject('Cannot wait on a promise that has ' + . 'no internal wait function. You must provide a wait ' + . 'function when constructing the promise to be able to ' + . 'wait on a promise.'); + } + + queue()->run(); + + if ($this->state === self::PENDING) { + $this->reject('Invoking the wait callback did not resolve the promise'); + } + } + + private function invokeWaitFn() + { + try { + $wfn = $this->waitFn; + $this->waitFn = null; + $wfn(true); + } catch (\Exception $reason) { + if ($this->state === self::PENDING) { + // The promise has not been resolved yet, so reject the promise + // with the exception. + $this->reject($reason); + } else { + // The promise was already resolved, so there's a problem in + // the application. + throw $reason; + } + } + } + + private function invokeWaitList() + { + $waitList = $this->waitList; + $this->waitList = null; + + foreach ($waitList as $result) { + descend: + $result->waitIfPending(); + if ($result->result instanceof Promise) { + $result = $result->result; + goto descend; + } + } + } +} diff --git a/Lib/Alisms/GuzzleHttp/Promise/PromiseInterface.php b/Lib/Alisms/GuzzleHttp/Promise/PromiseInterface.php new file mode 100644 index 0000000..8f5f4b9 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Promise/PromiseInterface.php @@ -0,0 +1,93 @@ +reason = $reason; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // If there's no onRejected callback then just return self. + if (!$onRejected) { + return $this; + } + + $queue = queue(); + $reason = $this->reason; + $p = new Promise([$queue, 'run']); + $queue->add(static function () use ($p, $reason, $onRejected) { + if ($p->getState() === self::PENDING) { + try { + // Return a resolved promise if onRejected does not throw. + $p->resolve($onRejected($reason)); + } catch (\Exception $e) { + // onRejected threw, so return a rejected promise. + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + if ($unwrap) { + throw exception_for($this->reason); + } + } + + public function getState() + { + return self::REJECTED; + } + + public function resolve($value) + { + throw new \LogicException("Cannot resolve a rejected promise"); + } + + public function reject($reason) + { + if ($reason !== $this->reason) { + throw new \LogicException("Cannot reject a rejected promise"); + } + } + + public function cancel() + { + // pass + } +} diff --git a/Lib/Alisms/GuzzleHttp/Promise/RejectionException.php b/Lib/Alisms/GuzzleHttp/Promise/RejectionException.php new file mode 100644 index 0000000..07c1136 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Promise/RejectionException.php @@ -0,0 +1,47 @@ +reason = $reason; + + $message = 'The promise was rejected'; + + if ($description) { + $message .= ' with reason: ' . $description; + } elseif (is_string($reason) + || (is_object($reason) && method_exists($reason, '__toString')) + ) { + $message .= ' with reason: ' . $this->reason; + } elseif ($reason instanceof \JsonSerializable) { + $message .= ' with reason: ' + . json_encode($this->reason, JSON_PRETTY_PRINT); + } + + parent::__construct($message); + } + + /** + * Returns the rejection reason. + * + * @return mixed + */ + public function getReason() + { + return $this->reason; + } +} diff --git a/Lib/Alisms/GuzzleHttp/Promise/TaskQueue.php b/Lib/Alisms/GuzzleHttp/Promise/TaskQueue.php new file mode 100644 index 0000000..5026363 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Promise/TaskQueue.php @@ -0,0 +1,79 @@ +run(); + */ +class TaskQueue +{ + private $enableShutdown = true; + private $queue = []; + + public function __construct($withShutdown = true) + { + if ($withShutdown) { + register_shutdown_function(function () { + if ($this->enableShutdown) { + // Only run the tasks if an E_ERROR didn't occur. + $err = error_get_last(); + if (!$err || ($err['type'] ^ E_ERROR)) { + $this->run(); + } + } + }); + } + } + + /** + * Returns true if the queue is empty. + * + * @return bool + */ + public function isEmpty() + { + return !$this->queue; + } + + /** + * Adds a task to the queue that will be executed the next time run is + * called. + * + * @param callable $task + */ + public function add(callable $task) + { + $this->queue[] = $task; + } + + /** + * Execute all of the pending task in the queue. + */ + public function run() + { + while ($task = array_shift($this->queue)) { + $task(); + } + } + + /** + * The task queue will be run and exhausted by default when the process + * exits IFF the exit is not the result of a PHP E_ERROR error. + * + * You can disable running the automatic shutdown of the queue by calling + * this function. If you disable the task queue shutdown process, then you + * MUST either run the task queue (as a result of running your event loop + * or manually using the run() method) or wait on each outstanding promise. + * + * Note: This shutdown will occur before any destructors are triggered. + */ + public function disableShutdown() + { + $this->enableShutdown = false; + } +} diff --git a/Lib/Alisms/GuzzleHttp/Promise/functions.php b/Lib/Alisms/GuzzleHttp/Promise/functions.php new file mode 100644 index 0000000..89c6569 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Promise/functions.php @@ -0,0 +1,495 @@ + + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\queue()->run(); + * } + * + * + * @return TaskQueue + */ +function queue() +{ + static $queue; + + if (!$queue) { + $queue = new TaskQueue(); + } + + return $queue; +} + +/** + * Adds a function to run in the task queue when it is next `run()` and returns + * a promise that is fulfilled or rejected with the result. + * + * @param callable $task Task function to run. + * + * @return PromiseInterface + */ +function task(callable $task) +{ + $queue = queue(); + $promise = new Promise([$queue, 'run']); + $queue->add(function () use ($task, $promise) { + try { + $promise->resolve($task()); + } catch (\Exception $e) { + $promise->reject($e); + } + }); + + return $promise; +} + +/** + * Creates a promise for a value if the value is not a promise. + * + * @param mixed $value Promise or value. + * + * @return PromiseInterface + */ +function promise_for($value) +{ + if ($value instanceof PromiseInterface) { + return $value; + } + + // Return a Guzzle promise that shadows the given promise. + if (method_exists($value, 'then')) { + $wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null; + $cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null; + $promise = new Promise($wfn, $cfn); + $value->then([$promise, 'resolve'], [$promise, 'reject']); + return $promise; + } + + return new FulfilledPromise($value); +} + +/** + * Creates a rejected promise for a reason if the reason is not a promise. If + * the provided reason is a promise, then it is returned as-is. + * + * @param mixed $reason Promise or reason. + * + * @return PromiseInterface + */ +function rejection_for($reason) +{ + if ($reason instanceof PromiseInterface) { + return $reason; + } + + return new RejectedPromise($reason); +} + +/** + * Create an exception for a rejected promise value. + * + * @param mixed $reason + * + * @return \Exception + */ +function exception_for($reason) +{ + return $reason instanceof \Exception + ? $reason + : new RejectionException($reason); +} + +/** + * Returns an iterator for the given value. + * + * @param mixed $value + * + * @return \Iterator + */ +function iter_for($value) +{ + if ($value instanceof \Iterator) { + return $value; + } elseif (is_array($value)) { + return new \ArrayIterator($value); + } else { + return new \ArrayIterator([$value]); + } +} + +/** + * Synchronously waits on a promise to resolve and returns an inspection state + * array. + * + * Returns a state associative array containing a "state" key mapping to a + * valid promise state. If the state of the promise is "fulfilled", the array + * will contain a "value" key mapping to the fulfilled value of the promise. If + * the promise is rejected, the array will contain a "reason" key mapping to + * the rejection reason of the promise. + * + * @param PromiseInterface $promise Promise or value. + * + * @return array + */ +function inspect(PromiseInterface $promise) +{ + try { + return [ + 'state' => PromiseInterface::FULFILLED, + 'value' => $promise->wait() + ]; + } catch (RejectionException $e) { + return ['state' => 'rejected', 'reason' => $e->getReason()]; + } catch (\Exception $e) { + return ['state' => 'rejected', 'reason' => $e]; + } +} + +/** + * Waits on all of the provided promises, but does not unwrap rejected promises + * as thrown exception. + * + * Returns an array of inspection state arrays. + * + * @param PromiseInterface[] $promises Traversable of promises to wait upon. + * + * @return array + * @see GuzzleHttp\Promise\inspect for the inspection state array format. + */ +function inspect_all($promises) +{ + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = inspect($promise); + } + + return $results; +} + +/** + * Waits on all of the provided promises and returns the fulfilled values. + * + * Returns an array that contains the value of each promise (in the same order + * the promises were provided). An exception is thrown if any of the promises + * are rejected. + * + * @param mixed $promises Iterable of PromiseInterface objects to wait on. + * + * @return array + * @throws \Exception on error + */ +function unwrap($promises) +{ + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = $promise->wait(); + } + + return $results; +} + +/** + * Given an array of promises, return a promise that is fulfilled when all the + * items in the array are fulfilled. + * + * The promise's fulfillment value is an array with fulfillment values at + * respective positions to the original array. If any promise in the array + * rejects, the returned promise is rejected with the rejection reason. + * + * @param mixed $promises Promises or values. + * + * @return Promise + */ +function all($promises) +{ + $results = []; + return each( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = $value; + }, + function ($reason, $idx, Promise $aggregate) { + $aggregate->reject($reason); + } + )->then(function () use (&$results) { + ksort($results); + return $results; + }); +} + +/** + * Initiate a competitive race between multiple promises or values (values will + * become immediately fulfilled promises). + * + * When count amount of promises have been fulfilled, the returned promise is + * fulfilled with an array that contains the fulfillment values of the winners + * in order of resolution. + * + * This prommise is rejected with a {@see GuzzleHttp\Promise\AggregateException} + * if the number of fulfilled promises is less than the desired $count. + * + * @param int $count Total number of promises. + * @param mixed $promises Promises or values. + * + * @return Promise + */ +function some($count, $promises) +{ + $results = []; + $rejections = []; + + return each( + $promises, + function ($value, $idx, PromiseInterface $p) use (&$results, $count) { + if ($p->getState() !== PromiseInterface::PENDING) { + return; + } + $results[$idx] = $value; + if (count($results) >= $count) { + $p->resolve(null); + } + }, + function ($reason) use (&$rejections) { + $rejections[] = $reason; + } + )->then( + function () use (&$results, &$rejections, $count) { + if (count($results) !== $count) { + throw new AggregateException( + 'Not enough promises to fulfill count', + $rejections + ); + } + ksort($results); + return array_values($results); + } + ); +} + +/** + * Like some(), with 1 as count. However, if the promise fulfills, the + * fulfillment value is not an array of 1 but the value directly. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ +function any($promises) +{ + return some(1, $promises)->then(function ($values) { return $values[0]; }); +} + +/** + * Returns a promise that is fulfilled when all of the provided promises have + * been fulfilled or rejected. + * + * The returned promise is fulfilled with an array of inspection state arrays. + * + * @param mixed $promises Promises or values. + * + * @return Promise + * @see GuzzleHttp\Promise\inspect for the inspection state array format. + */ +function settle($promises) +{ + $results = []; + + return each( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = ['state' => 'fulfilled', 'value' => $value]; + }, + function ($reason, $idx) use (&$results) { + $results[$idx] = ['state' => 'rejected', 'reason' => $reason]; + } + )->then(function () use (&$results) { + ksort($results); + return $results; + }); +} + +/** + * Given an iterator that yields promises or values, returns a promise that is + * fulfilled with a null value when the iterator has been consumed or the + * aggregate promise has been fulfilled or rejected. + * + * $onFulfilled is a function that accepts the fulfilled value, iterator + * index, and the aggregate promise. The callback can invoke any necessary side + * effects and choose to resolve or reject the aggregate promise if needed. + * + * $onRejected is a function that accepts the rejection reason, iterator + * index, and the aggregate promise. The callback can invoke any necessary side + * effects and choose to resolve or reject the aggregate promise if needed. + * + * @param mixed $iterable Iterator or array to iterate over. + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return Promise + */ +function each( + $iterable, + callable $onFulfilled = null, + callable $onRejected = null +) { + return (new EachPromise($iterable, [ + 'fulfilled' => $onFulfilled, + 'rejected' => $onRejected + ]))->promise(); +} + +/** + * Like each, but only allows a certain number of outstanding promises at any + * given time. + * + * $concurrency may be an integer or a function that accepts the number of + * pending promises and returns a numeric concurrency limit value to allow for + * dynamic a concurrency size. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return mixed + */ +function each_limit( + $iterable, + $concurrency, + callable $onFulfilled = null, + callable $onRejected = null +) { + return (new EachPromise($iterable, [ + 'fulfilled' => $onFulfilled, + 'rejected' => $onRejected, + 'concurrency' => $concurrency + ]))->promise(); +} + +/** + * Like each_limit, but ensures that no promise in the given $iterable argument + * is rejected. If any promise is rejected, then the aggregate promise is + * rejected with the encountered rejection. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * + * @return mixed + */ +function each_limit_all( + $iterable, + $concurrency, + callable $onFulfilled = null +) { + return each_limit( + $iterable, + $concurrency, + $onFulfilled, + function ($reason, $idx, PromiseInterface $aggregate) { + $aggregate->reject($reason); + } + ); +} + +/** + * Returns true if a promise is fulfilled. + * + * @param PromiseInterface $promise + * + * @return bool + */ +function is_fulfilled(PromiseInterface $promise) +{ + return $promise->getState() === PromiseInterface::FULFILLED; +} + +/** + * Returns true if a promise is rejected. + * + * @param PromiseInterface $promise + * + * @return bool + */ +function is_rejected(PromiseInterface $promise) +{ + return $promise->getState() === PromiseInterface::REJECTED; +} + +/** + * Returns true if a promise is fulfilled or rejected. + * + * @param PromiseInterface $promise + * + * @return bool + */ +function is_settled(PromiseInterface $promise) +{ + return $promise->getState() !== PromiseInterface::PENDING; +} + +/** + * Creates a promise that is resolved using a generator that yields values or + * promises (somewhat similar to C#'s async keyword). + * + * When called, the coroutine function will start an instance of the generator + * and returns a promise that is fulfilled with its final yielded value. + * + * Control is returned back to the generator when the yielded promise settles. + * This can lead to less verbose code when doing lots of sequential async calls + * with minimal processing in between. + * + * use GuzzleHttp\Promise; + * + * function createPromise($value) { + * return new Promise\FulfilledPromise($value); + * } + * + * $promise = Promise\coroutine(function () { + * $value = (yield createPromise('a')); + * try { + * $value = (yield createPromise($value . 'b')); + * } catch (\Exception $e) { + * // The promise was rejected. + * } + * yield $value . 'c'; + * }); + * + * // Outputs "abc" + * $promise->then(function ($v) { echo $v; }); + * + * @param callable $generatorFn Generator function to wrap into a promise. + * + * @return Promise + * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration + */ +function coroutine(callable $generatorFn) +{ + $generator = $generatorFn(); + return __next_coroutine($generator->current(), $generator)->then(); +} + +/** @internal */ +function __next_coroutine($yielded, \Generator $generator) +{ + return promise_for($yielded)->then( + function ($value) use ($generator) { + $nextYield = $generator->send($value); + return $generator->valid() + ? __next_coroutine($nextYield, $generator) + : $value; + }, + function ($reason) use ($generator) { + $nextYield = $generator->throw(exception_for($reason)); + // The throw was caught, so keep iterating on the coroutine + return __next_coroutine($nextYield, $generator); + } + ); +} diff --git a/Lib/Alisms/GuzzleHttp/Promise/functions_include.php b/Lib/Alisms/GuzzleHttp/Promise/functions_include.php new file mode 100644 index 0000000..34cd171 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Promise/functions_include.php @@ -0,0 +1,6 @@ +addStream($stream); + } + } + + public function __toString() + { + try { + $this->rewind(); + return $this->getContents(); + } catch (\Exception $e) { + return ''; + } + } + + /** + * Add a stream to the AppendStream + * + * @param StreamInterface $stream Stream to append. Must be readable. + * + * @throws \InvalidArgumentException if the stream is not readable + */ + public function addStream(StreamInterface $stream) + { + if (!$stream->isReadable()) { + throw new \InvalidArgumentException('Each stream must be readable'); + } + + // The stream is only seekable if all streams are seekable + if (!$stream->isSeekable()) { + $this->seekable = false; + } + + $this->streams[] = $stream; + } + + public function getContents() + { + return copy_to_string($this); + } + + /** + * Closes each attached stream. + * + * {@inheritdoc} + */ + public function close() + { + $this->pos = $this->current = 0; + + foreach ($this->streams as $stream) { + $stream->close(); + } + + $this->streams = []; + } + + /** + * Detaches each attached stream + * + * {@inheritdoc} + */ + public function detach() + { + $this->close(); + $this->detached = true; + } + + public function tell() + { + return $this->pos; + } + + /** + * Tries to calculate the size by adding the size of each stream. + * + * If any of the streams do not return a valid number, then the size of the + * append stream cannot be determined and null is returned. + * + * {@inheritdoc} + */ + public function getSize() + { + $size = 0; + + foreach ($this->streams as $stream) { + $s = $stream->getSize(); + if ($s === null) { + return null; + } + $size += $s; + } + + return $size; + } + + public function eof() + { + return !$this->streams || + ($this->current >= count($this->streams) - 1 && + $this->streams[$this->current]->eof()); + } + + public function rewind() + { + $this->seek(0); + } + + /** + * Attempts to seek to the given position. Only supports SEEK_SET. + * + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + if (!$this->seekable) { + throw new \RuntimeException('This AppendStream is not seekable'); + } elseif ($whence !== SEEK_SET) { + throw new \RuntimeException('The AppendStream can only seek with SEEK_SET'); + } + + $this->pos = $this->current = 0; + + // Rewind each stream + foreach ($this->streams as $i => $stream) { + try { + $stream->rewind(); + } catch (\Exception $e) { + throw new \RuntimeException('Unable to seek stream ' + . $i . ' of the AppendStream', 0, $e); + } + } + + // Seek to the actual position by reading from each stream + while ($this->pos < $offset && !$this->eof()) { + $result = $this->read(min(8096, $offset - $this->pos)); + if ($result === '') { + break; + } + } + } + + /** + * Reads from all of the appended streams until the length is met or EOF. + * + * {@inheritdoc} + */ + public function read($length) + { + $buffer = ''; + $total = count($this->streams) - 1; + $remaining = $length; + $progressToNext = false; + + while ($remaining > 0) { + + // Progress to the next stream if needed. + if ($progressToNext || $this->streams[$this->current]->eof()) { + $progressToNext = false; + if ($this->current === $total) { + break; + } + $this->current++; + } + + $result = $this->streams[$this->current]->read($remaining); + + // Using a loose comparison here to match on '', false, and null + if ($result == null) { + $progressToNext = true; + continue; + } + + $buffer .= $result; + $remaining = $length - strlen($buffer); + } + + $this->pos += strlen($buffer); + + return $buffer; + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return false; + } + + public function isSeekable() + { + return $this->seekable; + } + + public function write($string) + { + throw new \RuntimeException('Cannot write to an AppendStream'); + } + + public function getMetadata($key = null) + { + return $key ? null : []; + } +} diff --git a/Lib/Alisms/GuzzleHttp/Psr7/BufferStream.php b/Lib/Alisms/GuzzleHttp/Psr7/BufferStream.php new file mode 100644 index 0000000..af4d4c2 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Psr7/BufferStream.php @@ -0,0 +1,137 @@ +hwm = $hwm; + } + + public function __toString() + { + return $this->getContents(); + } + + public function getContents() + { + $buffer = $this->buffer; + $this->buffer = ''; + + return $buffer; + } + + public function close() + { + $this->buffer = ''; + } + + public function detach() + { + $this->close(); + } + + public function getSize() + { + return strlen($this->buffer); + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return true; + } + + public function isSeekable() + { + return false; + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + throw new \RuntimeException('Cannot seek a BufferStream'); + } + + public function eof() + { + return strlen($this->buffer) === 0; + } + + public function tell() + { + throw new \RuntimeException('Cannot determine the position of a BufferStream'); + } + + /** + * Reads data from the buffer. + */ + public function read($length) + { + $currentLength = strlen($this->buffer); + + if ($length >= $currentLength) { + // No need to slice the buffer because we don't have enough data. + $result = $this->buffer; + $this->buffer = ''; + } else { + // Slice up the result to provide a subset of the buffer. + $result = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + } + + return $result; + } + + /** + * Writes data to the buffer. + */ + public function write($string) + { + $this->buffer .= $string; + + // TODO: What should happen here? + if (strlen($this->buffer) >= $this->hwm) { + return false; + } + + return strlen($string); + } + + public function getMetadata($key = null) + { + if ($key == 'hwm') { + return $this->hwm; + } + + return $key ? null : []; + } +} diff --git a/Lib/Alisms/GuzzleHttp/Psr7/CachingStream.php b/Lib/Alisms/GuzzleHttp/Psr7/CachingStream.php new file mode 100644 index 0000000..796d581 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Psr7/CachingStream.php @@ -0,0 +1,136 @@ +remoteStream = $stream; + $this->stream = $target ?: new Stream(fopen('php://temp', 'r+')); + } + + public function getSize() + { + return max($this->stream->getSize(), $this->remoteStream->getSize()); + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + if ($whence == SEEK_SET) { + $byte = $offset; + } elseif ($whence == SEEK_CUR) { + $byte = $offset + $this->tell(); + } elseif ($whence == SEEK_END) { + $size = $this->remoteStream->getSize(); + if ($size === null) { + $size = $this->cacheEntireStream(); + } + // Because 0 is the first byte, we seek to size - 1. + $byte = $size - 1 - $offset; + } else { + throw new \InvalidArgumentException('Invalid whence'); + } + + $diff = $byte - $this->stream->getSize(); + + if ($diff > 0) { + // If the seek byte is greater the number of read bytes, then read + // the difference of bytes to cache the bytes and inherently seek. + $this->read($diff); + } else { + // We can just do a normal seek since we've already seen this byte. + $this->stream->seek($byte); + } + } + + public function read($length) + { + // Perform a regular read on any previously read data from the buffer + $data = $this->stream->read($length); + $remaining = $length - strlen($data); + + // More data was requested so read from the remote stream + if ($remaining) { + // If data was written to the buffer in a position that would have + // been filled from the remote stream, then we must skip bytes on + // the remote stream to emulate overwriting bytes from that + // position. This mimics the behavior of other PHP stream wrappers. + $remoteData = $this->remoteStream->read( + $remaining + $this->skipReadBytes + ); + + if ($this->skipReadBytes) { + $len = strlen($remoteData); + $remoteData = substr($remoteData, $this->skipReadBytes); + $this->skipReadBytes = max(0, $this->skipReadBytes - $len); + } + + $data .= $remoteData; + $this->stream->write($remoteData); + } + + return $data; + } + + public function write($string) + { + // When appending to the end of the currently read stream, you'll want + // to skip bytes from being read from the remote stream to emulate + // other stream wrappers. Basically replacing bytes of data of a fixed + // length. + $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); + if ($overflow > 0) { + $this->skipReadBytes += $overflow; + } + + return $this->stream->write($string); + } + + public function eof() + { + return $this->stream->eof() && $this->remoteStream->eof(); + } + + /** + * Close both the remote stream and buffer stream + */ + public function close() + { + $this->remoteStream->close() && $this->stream->close(); + } + + private function cacheEntireStream() + { + $target = new FnStream(['write' => 'strlen']); + copy_to_stream($this, $target); + + return $this->tell(); + } +} diff --git a/Lib/Alisms/GuzzleHttp/Psr7/DroppingStream.php b/Lib/Alisms/GuzzleHttp/Psr7/DroppingStream.php new file mode 100644 index 0000000..8935c80 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Psr7/DroppingStream.php @@ -0,0 +1,42 @@ +stream = $stream; + $this->maxLength = $maxLength; + } + + public function write($string) + { + $diff = $this->maxLength - $this->stream->getSize(); + + // Begin returning 0 when the underlying stream is too large. + if ($diff <= 0) { + return 0; + } + + // Write the stream or a subset of the stream if needed. + if (strlen($string) < $diff) { + return $this->stream->write($string); + } + + return $this->stream->write(substr($string, 0, $diff)); + } +} diff --git a/Lib/Alisms/GuzzleHttp/Psr7/FnStream.php b/Lib/Alisms/GuzzleHttp/Psr7/FnStream.php new file mode 100644 index 0000000..cc9b445 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Psr7/FnStream.php @@ -0,0 +1,149 @@ +methods = $methods; + + // Create the functions on the class + foreach ($methods as $name => $fn) { + $this->{'_fn_' . $name} = $fn; + } + } + + /** + * Lazily determine which methods are not implemented. + * @throws \BadMethodCallException + */ + public function __get($name) + { + throw new \BadMethodCallException(str_replace('_fn_', '', $name) + . '() is not implemented in the FnStream'); + } + + /** + * The close method is called on the underlying stream only if possible. + */ + public function __destruct() + { + if (isset($this->_fn_close)) { + call_user_func($this->_fn_close); + } + } + + /** + * Adds custom functionality to an underlying stream by intercepting + * specific method calls. + * + * @param StreamInterface $stream Stream to decorate + * @param array $methods Hash of method name to a closure + * + * @return FnStream + */ + public static function decorate(StreamInterface $stream, array $methods) + { + // If any of the required methods were not provided, then simply + // proxy to the decorated stream. + foreach (array_diff(self::$slots, array_keys($methods)) as $diff) { + $methods[$diff] = [$stream, $diff]; + } + + return new self($methods); + } + + public function __toString() + { + return call_user_func($this->_fn___toString); + } + + public function close() + { + return call_user_func($this->_fn_close); + } + + public function detach() + { + return call_user_func($this->_fn_detach); + } + + public function getSize() + { + return call_user_func($this->_fn_getSize); + } + + public function tell() + { + return call_user_func($this->_fn_tell); + } + + public function eof() + { + return call_user_func($this->_fn_eof); + } + + public function isSeekable() + { + return call_user_func($this->_fn_isSeekable); + } + + public function rewind() + { + call_user_func($this->_fn_rewind); + } + + public function seek($offset, $whence = SEEK_SET) + { + call_user_func($this->_fn_seek, $offset, $whence); + } + + public function isWritable() + { + return call_user_func($this->_fn_isWritable); + } + + public function write($string) + { + return call_user_func($this->_fn_write, $string); + } + + public function isReadable() + { + return call_user_func($this->_fn_isReadable); + } + + public function read($length) + { + return call_user_func($this->_fn_read, $length); + } + + public function getContents() + { + return call_user_func($this->_fn_getContents); + } + + public function getMetadata($key = null) + { + return call_user_func($this->_fn_getMetadata, $key); + } +} diff --git a/Lib/Alisms/GuzzleHttp/Psr7/InflateStream.php b/Lib/Alisms/GuzzleHttp/Psr7/InflateStream.php new file mode 100644 index 0000000..2c8628b --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Psr7/InflateStream.php @@ -0,0 +1,29 @@ +stream = new Stream($resource); + } +} diff --git a/Lib/Alisms/GuzzleHttp/Psr7/LazyOpenStream.php b/Lib/Alisms/GuzzleHttp/Psr7/LazyOpenStream.php new file mode 100644 index 0000000..02cec3a --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Psr7/LazyOpenStream.php @@ -0,0 +1,39 @@ +filename = $filename; + $this->mode = $mode; + } + + /** + * Creates the underlying stream lazily when required. + * + * @return StreamInterface + */ + protected function createStream() + { + return stream_for(try_fopen($this->filename, $this->mode)); + } +} diff --git a/Lib/Alisms/GuzzleHttp/Psr7/LimitStream.php b/Lib/Alisms/GuzzleHttp/Psr7/LimitStream.php new file mode 100644 index 0000000..7f2298b --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Psr7/LimitStream.php @@ -0,0 +1,155 @@ +stream = $stream; + $this->setLimit($limit); + $this->setOffset($offset); + } + + public function eof() + { + // Always return true if the underlying stream is EOF + if ($this->stream->eof()) { + return true; + } + + // No limit and the underlying stream is not at EOF + if ($this->limit == -1) { + return false; + } + + return $this->stream->tell() >= $this->offset + $this->limit; + } + + /** + * Returns the size of the limited subset of data + * {@inheritdoc} + */ + public function getSize() + { + if (null === ($length = $this->stream->getSize())) { + return null; + } elseif ($this->limit == -1) { + return $length - $this->offset; + } else { + return min($this->limit, $length - $this->offset); + } + } + + /** + * Allow for a bounded seek on the read limited stream + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + if ($whence !== SEEK_SET || $offset < 0) { + throw new \RuntimeException(sprintf( + 'Cannot seek to offset % with whence %s', + $offset, + $whence + )); + } + + $offset += $this->offset; + + if ($this->limit !== -1) { + if ($offset > $this->offset + $this->limit) { + $offset = $this->offset + $this->limit; + } + } + + $this->stream->seek($offset); + } + + /** + * Give a relative tell() + * {@inheritdoc} + */ + public function tell() + { + return $this->stream->tell() - $this->offset; + } + + /** + * Set the offset to start limiting from + * + * @param int $offset Offset to seek to and begin byte limiting from + * + * @throws \RuntimeException if the stream cannot be seeked. + */ + public function setOffset($offset) + { + $current = $this->stream->tell(); + + if ($current !== $offset) { + // If the stream cannot seek to the offset position, then read to it + if ($this->stream->isSeekable()) { + $this->stream->seek($offset); + } elseif ($current > $offset) { + throw new \RuntimeException("Could not seek to stream offset $offset"); + } else { + $this->stream->read($offset - $current); + } + } + + $this->offset = $offset; + } + + /** + * Set the limit of bytes that the decorator allows to be read from the + * stream. + * + * @param int $limit Number of bytes to allow to be read from the stream. + * Use -1 for no limit. + */ + public function setLimit($limit) + { + $this->limit = $limit; + } + + public function read($length) + { + if ($this->limit == -1) { + return $this->stream->read($length); + } + + // Check if the current position is less than the total allowed + // bytes + original offset + $remaining = ($this->offset + $this->limit) - $this->stream->tell(); + if ($remaining > 0) { + // Only return the amount of requested data, ensuring that the byte + // limit is not exceeded + return $this->stream->read(min($remaining, $length)); + } + + return ''; + } +} diff --git a/Lib/Alisms/GuzzleHttp/Psr7/MessageTrait.php b/Lib/Alisms/GuzzleHttp/Psr7/MessageTrait.php new file mode 100644 index 0000000..123205c --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Psr7/MessageTrait.php @@ -0,0 +1,158 @@ +protocol; + } + + public function withProtocolVersion($version) + { + if ($this->protocol === $version) { + return $this; + } + + $new = clone $this; + $new->protocol = $version; + return $new; + } + + public function getHeaders() + { + return $this->headerLines; + } + + public function hasHeader($header) + { + return isset($this->headers[strtolower($header)]); + } + + public function getHeader($header) + { + $name = strtolower($header); + return isset($this->headers[$name]) ? $this->headers[$name] : []; + } + + public function getHeaderLine($header) + { + return implode(', ', $this->getHeader($header)); + } + + public function withHeader($header, $value) + { + $new = clone $this; + $header = trim($header); + $name = strtolower($header); + + if (!is_array($value)) { + $new->headers[$name] = [trim($value)]; + } else { + $new->headers[$name] = $value; + foreach ($new->headers[$name] as &$v) { + $v = trim($v); + } + } + + // Remove the header lines. + foreach (array_keys($new->headerLines) as $key) { + if (strtolower($key) === $name) { + unset($new->headerLines[$key]); + } + } + + // Add the header line. + $new->headerLines[$header] = $new->headers[$name]; + + return $new; + } + + public function withAddedHeader($header, $value) + { + if (!$this->hasHeader($header)) { + return $this->withHeader($header, $value); + } + + $new = clone $this; + $new->headers[strtolower($header)][] = $value; + $new->headerLines[$header][] = $value; + return $new; + } + + public function withoutHeader($header) + { + if (!$this->hasHeader($header)) { + return $this; + } + + $new = clone $this; + $name = strtolower($header); + unset($new->headers[$name]); + + foreach (array_keys($new->headerLines) as $key) { + if (strtolower($key) === $name) { + unset($new->headerLines[$key]); + } + } + + return $new; + } + + public function getBody() + { + if (!$this->stream) { + $this->stream = stream_for(''); + } + + return $this->stream; + } + + public function withBody(StreamInterface $body) + { + if ($body === $this->stream) { + return $this; + } + + $new = clone $this; + $new->stream = $body; + return $new; + } + + private function setHeaders(array $headers) + { + $this->headerLines = $this->headers = []; + foreach ($headers as $header => $value) { + $header = trim($header); + $name = strtolower($header); + if (!is_array($value)) { + $value = trim($value); + $this->headers[$name][] = $value; + $this->headerLines[$header][] = $value; + } else { + foreach ($value as $v) { + $v = trim($v); + $this->headers[$name][] = $v; + $this->headerLines[$header][] = $v; + } + } + } + } +} diff --git a/Lib/Alisms/GuzzleHttp/Psr7/MultipartStream.php b/Lib/Alisms/GuzzleHttp/Psr7/MultipartStream.php new file mode 100644 index 0000000..fd006ec --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Psr7/MultipartStream.php @@ -0,0 +1,153 @@ +boundary = $boundary ?: uniqid(); + $this->stream = $this->createStream($elements); + } + + /** + * Get the boundary + * + * @return string + */ + public function getBoundary() + { + return $this->boundary; + } + + public function isWritable() + { + return false; + } + + /** + * Get the headers needed before transferring the content of a POST file + */ + private function getHeaders(array $headers) + { + $str = ''; + foreach ($headers as $key => $value) { + $str .= "{$key}: {$value}\r\n"; + } + + return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n"; + } + + /** + * Create the aggregate stream that will be used to upload the POST data + */ + protected function createStream(array $elements) + { + $stream = new AppendStream(); + + foreach ($elements as $element) { + $this->addElement($stream, $element); + } + + // Add the trailing boundary with CRLF + $stream->addStream(stream_for("--{$this->boundary}--\r\n")); + + return $stream; + } + + private function addElement(AppendStream $stream, array $element) + { + foreach (['contents', 'name'] as $key) { + if (!array_key_exists($key, $element)) { + throw new \InvalidArgumentException("A '{$key}' key is required"); + } + } + + $element['contents'] = stream_for($element['contents']); + + if (empty($element['filename'])) { + $uri = $element['contents']->getMetadata('uri'); + if (substr($uri, 0, 6) !== 'php://') { + $element['filename'] = $uri; + } + } + + list($body, $headers) = $this->createElement( + $element['name'], + $element['contents'], + isset($element['filename']) ? $element['filename'] : null, + isset($element['headers']) ? $element['headers'] : [] + ); + + $stream->addStream(stream_for($this->getHeaders($headers))); + $stream->addStream($body); + $stream->addStream(stream_for("\r\n")); + } + + /** + * @return array + */ + private function createElement($name, $stream, $filename, array $headers) + { + // Set a default content-disposition header if one was no provided + $disposition = $this->getHeader($headers, 'content-disposition'); + if (!$disposition) { + $headers['Content-Disposition'] = $filename + ? sprintf('form-data; name="%s"; filename="%s"', + $name, + basename($filename)) + : "form-data; name=\"{$name}\""; + } + + // Set a default content-length header if one was no provided + $length = $this->getHeader($headers, 'content-length'); + if (!$length) { + if ($length = $stream->getSize()) { + $headers['Content-Length'] = (string) $length; + } + } + + // Set a default Content-Type if one was not supplied + $type = $this->getHeader($headers, 'content-type'); + if (!$type && $filename) { + if ($type = mimetype_from_filename($filename)) { + $headers['Content-Type'] = $type; + } + } + + return [$stream, $headers]; + } + + private function getHeader(array $headers, $key) + { + $lowercaseHeader = strtolower($key); + foreach ($headers as $k => $v) { + if (strtolower($k) === $lowercaseHeader) { + return $v; + } + } + + return null; + } +} diff --git a/Lib/Alisms/GuzzleHttp/Psr7/NoSeekStream.php b/Lib/Alisms/GuzzleHttp/Psr7/NoSeekStream.php new file mode 100644 index 0000000..2332218 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Psr7/NoSeekStream.php @@ -0,0 +1,22 @@ +source = $source; + $this->size = isset($options['size']) ? $options['size'] : null; + $this->metadata = isset($options['metadata']) ? $options['metadata'] : []; + $this->buffer = new BufferStream(); + } + + public function __toString() + { + try { + return copy_to_string($this); + } catch (\Exception $e) { + return ''; + } + } + + public function close() + { + $this->detach(); + } + + public function detach() + { + $this->tellPos = false; + $this->source = null; + } + + public function getSize() + { + return $this->size; + } + + public function tell() + { + return $this->tellPos; + } + + public function eof() + { + return !$this->source; + } + + public function isSeekable() + { + return false; + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + throw new \RuntimeException('Cannot seek a PumpStream'); + } + + public function isWritable() + { + return false; + } + + public function write($string) + { + throw new \RuntimeException('Cannot write to a PumpStream'); + } + + public function isReadable() + { + return true; + } + + public function read($length) + { + $data = $this->buffer->read($length); + $readLen = strlen($data); + $this->tellPos += $readLen; + $remaining = $length - $readLen; + + if ($remaining) { + $this->pump($remaining); + $data .= $this->buffer->read($remaining); + $this->tellPos += strlen($data) - $readLen; + } + + return $data; + } + + public function getContents() + { + $result = ''; + while (!$this->eof()) { + $result .= $this->read(1000000); + } + + return $result; + } + + public function getMetadata($key = null) + { + if (!$key) { + return $this->metadata; + } + + return isset($this->metadata[$key]) ? $this->metadata[$key] : null; + } + + private function pump($length) + { + if ($this->source) { + do { + $data = call_user_func($this->source, $length); + if ($data === false || $data === null) { + $this->source = null; + return; + } + $this->buffer->write($data); + $length -= strlen($data); + } while ($length > 0); + } + } +} diff --git a/Lib/Alisms/GuzzleHttp/Psr7/Request.php b/Lib/Alisms/GuzzleHttp/Psr7/Request.php new file mode 100644 index 0000000..0189b14 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Psr7/Request.php @@ -0,0 +1,149 @@ +method = strtoupper($method); + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $protocolVersion; + + $host = $uri->getHost(); + if ($host && !$this->hasHeader('Host')) { + $this->updateHostFromUri($host); + } + + if ($body) { + $this->stream = stream_for($body); + } + } + + public function getRequestTarget() + { + if ($this->requestTarget !== null) { + return $this->requestTarget; + } + + $target = $this->uri->getPath(); + if ($target == null) { + $target = '/'; + } + if ($this->uri->getQuery()) { + $target .= '?' . $this->uri->getQuery(); + } + + return $target; + } + + public function withRequestTarget($requestTarget) + { + if (preg_match('#\s#', $requestTarget)) { + throw new InvalidArgumentException( + 'Invalid request target provided; cannot contain whitespace' + ); + } + + $new = clone $this; + $new->requestTarget = $requestTarget; + return $new; + } + + public function getMethod() + { + return $this->method; + } + + public function withMethod($method) + { + $new = clone $this; + $new->method = strtoupper($method); + return $new; + } + + public function getUri() + { + return $this->uri; + } + + public function withUri(UriInterface $uri, $preserveHost = false) + { + if ($uri === $this->uri) { + return $this; + } + + $new = clone $this; + $new->uri = $uri; + + if (!$preserveHost) { + if ($host = $uri->getHost()) { + $new->updateHostFromUri($host); + } + } + + return $new; + } + + public function withHeader($header, $value) + { + /** @var Request $newInstance */ + $newInstance = $this->withParentHeader($header, $value); + return $newInstance; + } + + private function updateHostFromUri($host) + { + // Ensure Host is the first header. + // See: http://tools.ietf.org/html/rfc7230#section-5.4 + if ($port = $this->uri->getPort()) { + $host .= ':' . $port; + } + + $this->headerLines = ['Host' => [$host]] + $this->headerLines; + $this->headers = ['host' => [$host]] + $this->headers; + } +} diff --git a/Lib/Alisms/GuzzleHttp/Psr7/Response.php b/Lib/Alisms/GuzzleHttp/Psr7/Response.php new file mode 100644 index 0000000..c94bf8f --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Psr7/Response.php @@ -0,0 +1,130 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-status', + 208 => 'Already Reported', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Switch Proxy', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Requested range not satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 511 => 'Network Authentication Required', + ]; + + /** @var null|string */ + private $reasonPhrase = ''; + + /** @var int */ + private $statusCode = 200; + + /** + * @param int $status Status code for the response, if any. + * @param array $headers Headers for the response, if any. + * @param mixed $body Stream body. + * @param string $version Protocol version. + * @param string $reason Reason phrase (a default will be used if possible). + */ + public function __construct( + $status = 200, + array $headers = [], + $body = null, + $version = '1.1', + $reason = null + ) { + $this->statusCode = (int) $status; + + if ($body !== null) { + $this->stream = stream_for($body); + } + + $this->setHeaders($headers); + if (!$reason && isset(self::$phrases[$this->statusCode])) { + $this->reasonPhrase = self::$phrases[$status]; + } else { + $this->reasonPhrase = (string) $reason; + } + + $this->protocol = $version; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getReasonPhrase() + { + return $this->reasonPhrase; + } + + public function withStatus($code, $reasonPhrase = '') + { + $new = clone $this; + $new->statusCode = (int) $code; + if (!$reasonPhrase && isset(self::$phrases[$new->statusCode])) { + $reasonPhrase = self::$phrases[$new->statusCode]; + } + $new->reasonPhrase = $reasonPhrase; + return $new; + } +} diff --git a/Lib/Alisms/GuzzleHttp/Psr7/Stream.php b/Lib/Alisms/GuzzleHttp/Psr7/Stream.php new file mode 100644 index 0000000..0b0db01 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Psr7/Stream.php @@ -0,0 +1,245 @@ + [ + 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, + 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, + 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a+' => true + ], + 'write' => [ + 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, + 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, + 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true + ] + ]; + + /** + * This constructor accepts an associative array of options. + * + * - size: (int) If a read stream would otherwise have an indeterminate + * size, but the size is known due to foreknownledge, then you can + * provide that size, in bytes. + * - metadata: (array) Any additional metadata to return when the metadata + * of the stream is accessed. + * + * @param resource $stream Stream resource to wrap. + * @param array $options Associative array of options. + * + * @throws \InvalidArgumentException if the stream is not a stream resource + */ + public function __construct($stream, $options = []) + { + if (!is_resource($stream)) { + throw new \InvalidArgumentException('Stream must be a resource'); + } + + if (isset($options['size'])) { + $this->size = $options['size']; + } + + $this->customMetadata = isset($options['metadata']) + ? $options['metadata'] + : []; + + $this->stream = $stream; + $meta = stream_get_meta_data($this->stream); + $this->seekable = $meta['seekable']; + $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]); + $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]); + $this->uri = $this->getMetadata('uri'); + } + + public function __get($name) + { + if ($name == 'stream') { + throw new \RuntimeException('The stream is detached'); + } + + throw new \BadMethodCallException('No value for ' . $name); + } + + /** + * Closes the stream when the destructed + */ + public function __destruct() + { + $this->close(); + } + + public function __toString() + { + try { + $this->seek(0); + return (string) stream_get_contents($this->stream); + } catch (\Exception $e) { + return ''; + } + } + + public function getContents() + { + $contents = stream_get_contents($this->stream); + + if ($contents === false) { + throw new \RuntimeException('Unable to read stream contents'); + } + + return $contents; + } + + public function close() + { + if (isset($this->stream)) { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->detach(); + } + } + + public function detach() + { + if (!isset($this->stream)) { + return null; + } + + $result = $this->stream; + unset($this->stream); + $this->size = $this->uri = null; + $this->readable = $this->writable = $this->seekable = false; + + return $result; + } + + public function getSize() + { + if ($this->size !== null) { + return $this->size; + } + + if (!isset($this->stream)) { + return null; + } + + // Clear the stat cache if the stream has a URI + if ($this->uri) { + clearstatcache(true, $this->uri); + } + + $stats = fstat($this->stream); + if (isset($stats['size'])) { + $this->size = $stats['size']; + return $this->size; + } + + return null; + } + + public function isReadable() + { + return $this->readable; + } + + public function isWritable() + { + return $this->writable; + } + + public function isSeekable() + { + return $this->seekable; + } + + public function eof() + { + return !$this->stream || feof($this->stream); + } + + public function tell() + { + $result = ftell($this->stream); + + if ($result === false) { + throw new \RuntimeException('Unable to determine stream position'); + } + + return $result; + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + if (!$this->seekable) { + throw new \RuntimeException('Stream is not seekable'); + } elseif (fseek($this->stream, $offset, $whence) === -1) { + throw new \RuntimeException('Unable to seek to stream position ' + . $offset . ' with whence ' . var_export($whence, true)); + } + } + + public function read($length) + { + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + + return fread($this->stream, $length); + } + + public function write($string) + { + if (!$this->writable) { + throw new \RuntimeException('Cannot write to a non-writable stream'); + } + + // We can't know the size after writing anything + $this->size = null; + $result = fwrite($this->stream, $string); + + if ($result === false) { + throw new \RuntimeException('Unable to write to stream'); + } + + return $result; + } + + public function getMetadata($key = null) + { + if (!isset($this->stream)) { + return $key ? null : []; + } elseif (!$key) { + return $this->customMetadata + stream_get_meta_data($this->stream); + } elseif (isset($this->customMetadata[$key])) { + return $this->customMetadata[$key]; + } + + $meta = stream_get_meta_data($this->stream); + + return isset($meta[$key]) ? $meta[$key] : null; + } +} diff --git a/Lib/Alisms/GuzzleHttp/Psr7/StreamDecoratorTrait.php b/Lib/Alisms/GuzzleHttp/Psr7/StreamDecoratorTrait.php new file mode 100644 index 0000000..daec6f5 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Psr7/StreamDecoratorTrait.php @@ -0,0 +1,149 @@ +stream = $stream; + } + + /** + * Magic method used to create a new stream if streams are not added in + * the constructor of a decorator (e.g., LazyOpenStream). + * + * @param string $name Name of the property (allows "stream" only). + * + * @return StreamInterface + */ + public function __get($name) + { + if ($name == 'stream') { + $this->stream = $this->createStream(); + return $this->stream; + } + + throw new \UnexpectedValueException("$name not found on class"); + } + + public function __toString() + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Exception $e) { + // Really, PHP? https://bugs.php.net/bug.php?id=53648 + trigger_error('StreamDecorator::__toString exception: ' + . (string) $e, E_USER_ERROR); + return ''; + } + } + + public function getContents() + { + return copy_to_string($this); + } + + /** + * Allow decorators to implement custom methods + * + * @param string $method Missing method name + * @param array $args Method arguments + * + * @return mixed + */ + public function __call($method, array $args) + { + $result = call_user_func_array([$this->stream, $method], $args); + + // Always return the wrapped object if the result is a return $this + return $result === $this->stream ? $this : $result; + } + + public function close() + { + $this->stream->close(); + } + + public function getMetadata($key = null) + { + return $this->stream->getMetadata($key); + } + + public function detach() + { + return $this->stream->detach(); + } + + public function getSize() + { + return $this->stream->getSize(); + } + + public function eof() + { + return $this->stream->eof(); + } + + public function tell() + { + return $this->stream->tell(); + } + + public function isReadable() + { + return $this->stream->isReadable(); + } + + public function isWritable() + { + return $this->stream->isWritable(); + } + + public function isSeekable() + { + return $this->stream->isSeekable(); + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + $this->stream->seek($offset, $whence); + } + + public function read($length) + { + return $this->stream->read($length); + } + + public function write($string) + { + return $this->stream->write($string); + } + + /** + * Implement in subclasses to dynamically create streams when requested. + * + * @return StreamInterface + * @throws \BadMethodCallException + */ + protected function createStream() + { + throw new \BadMethodCallException('Not implemented'); + } +} diff --git a/Lib/Alisms/GuzzleHttp/Psr7/StreamWrapper.php b/Lib/Alisms/GuzzleHttp/Psr7/StreamWrapper.php new file mode 100644 index 0000000..cf7b223 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Psr7/StreamWrapper.php @@ -0,0 +1,121 @@ +isReadable()) { + $mode = $stream->isWritable() ? 'r+' : 'r'; + } elseif ($stream->isWritable()) { + $mode = 'w'; + } else { + throw new \InvalidArgumentException('The stream must be readable, ' + . 'writable, or both.'); + } + + return fopen('guzzle://stream', $mode, null, stream_context_create([ + 'guzzle' => ['stream' => $stream] + ])); + } + + /** + * Registers the stream wrapper if needed + */ + public static function register() + { + if (!in_array('guzzle', stream_get_wrappers())) { + stream_wrapper_register('guzzle', __CLASS__); + } + } + + public function stream_open($path, $mode, $options, &$opened_path) + { + $options = stream_context_get_options($this->context); + + if (!isset($options['guzzle']['stream'])) { + return false; + } + + $this->mode = $mode; + $this->stream = $options['guzzle']['stream']; + + return true; + } + + public function stream_read($count) + { + return $this->stream->read($count); + } + + public function stream_write($data) + { + return (int) $this->stream->write($data); + } + + public function stream_tell() + { + return $this->stream->tell(); + } + + public function stream_eof() + { + return $this->stream->eof(); + } + + public function stream_seek($offset, $whence) + { + $this->stream->seek($offset, $whence); + + return true; + } + + public function stream_stat() + { + static $modeMap = [ + 'r' => 33060, + 'r+' => 33206, + 'w' => 33188 + ]; + + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => $modeMap[$this->mode], + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => $this->stream->getSize() ?: 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0 + ]; + } +} diff --git a/Lib/Alisms/GuzzleHttp/Psr7/Uri.php b/Lib/Alisms/GuzzleHttp/Psr7/Uri.php new file mode 100644 index 0000000..d428f2e --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Psr7/Uri.php @@ -0,0 +1,599 @@ + 80, + 'https' => 443, + ]; + + private static $charUnreserved = 'a-zA-Z0-9_\-\.~'; + private static $charSubDelims = '!\$&\'\(\)\*\+,;='; + private static $replaceQuery = ['=' => '%3D', '&' => '%26']; + + /** @var string Uri scheme. */ + private $scheme = ''; + + /** @var string Uri user info. */ + private $userInfo = ''; + + /** @var string Uri host. */ + private $host = ''; + + /** @var int|null Uri port. */ + private $port; + + /** @var string Uri path. */ + private $path = ''; + + /** @var string Uri query string. */ + private $query = ''; + + /** @var string Uri fragment. */ + private $fragment = ''; + + /** + * @param string $uri URI to parse and wrap. + */ + public function __construct($uri = '') + { + if ($uri != null) { + $parts = parse_url($uri); + if ($parts === false) { + throw new \InvalidArgumentException("Unable to parse URI: $uri"); + } + $this->applyParts($parts); + } + } + + public function __toString() + { + return self::createUriString( + $this->scheme, + $this->getAuthority(), + $this->getPath(), + $this->query, + $this->fragment + ); + } + + /** + * Removes dot segments from a path and returns the new path. + * + * @param string $path + * + * @return string + * @link http://tools.ietf.org/html/rfc3986#section-5.2.4 + */ + public static function removeDotSegments($path) + { + static $noopPaths = ['' => true, '/' => true, '*' => true]; + static $ignoreSegments = ['.' => true, '..' => true]; + + if (isset($noopPaths[$path])) { + return $path; + } + + $results = []; + $segments = explode('/', $path); + foreach ($segments as $segment) { + if ($segment == '..') { + array_pop($results); + } elseif (!isset($ignoreSegments[$segment])) { + $results[] = $segment; + } + } + + $newPath = implode('/', $results); + // Add the leading slash if necessary + if (substr($path, 0, 1) === '/' && + substr($newPath, 0, 1) !== '/' + ) { + $newPath = '/' . $newPath; + } + + // Add the trailing slash if necessary + if ($newPath != '/' && isset($ignoreSegments[end($segments)])) { + $newPath .= '/'; + } + + return $newPath; + } + + /** + * Resolve a base URI with a relative URI and return a new URI. + * + * @param UriInterface $base Base URI + * @param string $rel Relative URI + * + * @return UriInterface + */ + public static function resolve(UriInterface $base, $rel) + { + if ($rel === null || $rel === '') { + return $base; + } + + if (!($rel instanceof UriInterface)) { + $rel = new self($rel); + } + + // Return the relative uri as-is if it has a scheme. + if ($rel->getScheme()) { + return $rel->withPath(static::removeDotSegments($rel->getPath())); + } + + $relParts = [ + 'scheme' => $rel->getScheme(), + 'authority' => $rel->getAuthority(), + 'path' => $rel->getPath(), + 'query' => $rel->getQuery(), + 'fragment' => $rel->getFragment() + ]; + + $parts = [ + 'scheme' => $base->getScheme(), + 'authority' => $base->getAuthority(), + 'path' => $base->getPath(), + 'query' => $base->getQuery(), + 'fragment' => $base->getFragment() + ]; + + if (!empty($relParts['authority'])) { + $parts['authority'] = $relParts['authority']; + $parts['path'] = self::removeDotSegments($relParts['path']); + $parts['query'] = $relParts['query']; + $parts['fragment'] = $relParts['fragment']; + } elseif (!empty($relParts['path'])) { + if (substr($relParts['path'], 0, 1) == '/') { + $parts['path'] = self::removeDotSegments($relParts['path']); + $parts['query'] = $relParts['query']; + $parts['fragment'] = $relParts['fragment']; + } else { + if (!empty($parts['authority']) && empty($parts['path'])) { + $mergedPath = '/'; + } else { + $mergedPath = substr($parts['path'], 0, strrpos($parts['path'], '/') + 1); + } + $parts['path'] = self::removeDotSegments($mergedPath . $relParts['path']); + $parts['query'] = $relParts['query']; + $parts['fragment'] = $relParts['fragment']; + } + } elseif (!empty($relParts['query'])) { + $parts['query'] = $relParts['query']; + } elseif ($relParts['fragment'] != null) { + $parts['fragment'] = $relParts['fragment']; + } + + return new self(static::createUriString( + $parts['scheme'], + $parts['authority'], + $parts['path'], + $parts['query'], + $parts['fragment'] + )); + } + + /** + * Create a new URI with a specific query string value removed. + * + * Any existing query string values that exactly match the provided key are + * removed. + * + * Note: this function will convert "=" to "%3D" and "&" to "%26". + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Query string key value pair to remove. + * + * @return UriInterface + */ + public static function withoutQueryValue(UriInterface $uri, $key) + { + $current = $uri->getQuery(); + if (!$current) { + return $uri; + } + + $result = []; + foreach (explode('&', $current) as $part) { + if (explode('=', $part)[0] !== $key) { + $result[] = $part; + }; + } + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Create a new URI with a specific query string value. + * + * Any existing query string values that exactly match the provided key are + * removed and replaced with the given key value pair. + * + * Note: this function will convert "=" to "%3D" and "&" to "%26". + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Key to set. + * @param string $value Value to set. + * + * @return UriInterface + */ + public static function withQueryValue(UriInterface $uri, $key, $value) + { + $current = $uri->getQuery(); + $key = strtr($key, self::$replaceQuery); + + if (!$current) { + $result = []; + } else { + $result = []; + foreach (explode('&', $current) as $part) { + if (explode('=', $part)[0] !== $key) { + $result[] = $part; + }; + } + } + + if ($value !== null) { + $result[] = $key . '=' . strtr($value, self::$replaceQuery); + } else { + $result[] = $key; + } + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Create a URI from a hash of parse_url parts. + * + * @param array $parts + * + * @return self + */ + public static function fromParts(array $parts) + { + $uri = new self(); + $uri->applyParts($parts); + return $uri; + } + + public function getScheme() + { + return $this->scheme; + } + + public function getAuthority() + { + if (empty($this->host)) { + return ''; + } + + $authority = $this->host; + if (!empty($this->userInfo)) { + $authority = $this->userInfo . '@' . $authority; + } + + if ($this->isNonStandardPort($this->scheme, $this->host, $this->port)) { + $authority .= ':' . $this->port; + } + + return $authority; + } + + public function getUserInfo() + { + return $this->userInfo; + } + + public function getHost() + { + return $this->host; + } + + public function getPort() + { + return $this->port; + } + + public function getPath() + { + return $this->path == null ? '' : $this->path; + } + + public function getQuery() + { + return $this->query; + } + + public function getFragment() + { + return $this->fragment; + } + + public function withScheme($scheme) + { + $scheme = $this->filterScheme($scheme); + + if ($this->scheme === $scheme) { + return $this; + } + + $new = clone $this; + $new->scheme = $scheme; + $new->port = $new->filterPort($new->scheme, $new->host, $new->port); + return $new; + } + + public function withUserInfo($user, $password = null) + { + $info = $user; + if ($password) { + $info .= ':' . $password; + } + + if ($this->userInfo === $info) { + return $this; + } + + $new = clone $this; + $new->userInfo = $info; + return $new; + } + + public function withHost($host) + { + if ($this->host === $host) { + return $this; + } + + $new = clone $this; + $new->host = $host; + return $new; + } + + public function withPort($port) + { + $port = $this->filterPort($this->scheme, $this->host, $port); + + if ($this->port === $port) { + return $this; + } + + $new = clone $this; + $new->port = $port; + return $new; + } + + public function withPath($path) + { + if (!is_string($path)) { + throw new \InvalidArgumentException( + 'Invalid path provided; must be a string' + ); + } + + $path = $this->filterPath($path); + + if ($this->path === $path) { + return $this; + } + + $new = clone $this; + $new->path = $path; + return $new; + } + + public function withQuery($query) + { + if (!is_string($query) && !method_exists($query, '__toString')) { + throw new \InvalidArgumentException( + 'Query string must be a string' + ); + } + + $query = (string) $query; + if (substr($query, 0, 1) === '?') { + $query = substr($query, 1); + } + + $query = $this->filterQueryAndFragment($query); + + if ($this->query === $query) { + return $this; + } + + $new = clone $this; + $new->query = $query; + return $new; + } + + public function withFragment($fragment) + { + if (substr($fragment, 0, 1) === '#') { + $fragment = substr($fragment, 1); + } + + $fragment = $this->filterQueryAndFragment($fragment); + + if ($this->fragment === $fragment) { + return $this; + } + + $new = clone $this; + $new->fragment = $fragment; + return $new; + } + + /** + * Apply parse_url parts to a URI. + * + * @param $parts Array of parse_url parts to apply. + */ + private function applyParts(array $parts) + { + $this->scheme = isset($parts['scheme']) + ? $this->filterScheme($parts['scheme']) + : ''; + $this->userInfo = isset($parts['user']) ? $parts['user'] : ''; + $this->host = isset($parts['host']) ? $parts['host'] : ''; + $this->port = !empty($parts['port']) + ? $this->filterPort($this->scheme, $this->host, $parts['port']) + : null; + $this->path = isset($parts['path']) + ? $this->filterPath($parts['path']) + : ''; + $this->query = isset($parts['query']) + ? $this->filterQueryAndFragment($parts['query']) + : ''; + $this->fragment = isset($parts['fragment']) + ? $this->filterQueryAndFragment($parts['fragment']) + : ''; + if (isset($parts['pass'])) { + $this->userInfo .= ':' . $parts['pass']; + } + } + + /** + * Create a URI string from its various parts + * + * @param string $scheme + * @param string $authority + * @param string $path + * @param string $query + * @param string $fragment + * @return string + */ + private static function createUriString($scheme, $authority, $path, $query, $fragment) + { + $uri = ''; + + if (!empty($scheme)) { + $uri .= $scheme . '://'; + } + + if (!empty($authority)) { + $uri .= $authority; + } + + if ($path != null) { + // Add a leading slash if necessary. + if ($uri && substr($path, 0, 1) !== '/') { + $uri .= '/'; + } + $uri .= $path; + } + + if ($query != null) { + $uri .= '?' . $query; + } + + if ($fragment != null) { + $uri .= '#' . $fragment; + } + + return $uri; + } + + /** + * Is a given port non-standard for the current scheme? + * + * @param string $scheme + * @param string $host + * @param int $port + * @return bool + */ + private static function isNonStandardPort($scheme, $host, $port) + { + if (!$scheme && $port) { + return true; + } + + if (!$host || !$port) { + return false; + } + + return !isset(static::$schemes[$scheme]) || $port !== static::$schemes[$scheme]; + } + + /** + * @param string $scheme + * + * @return string + */ + private function filterScheme($scheme) + { + $scheme = strtolower($scheme); + $scheme = rtrim($scheme, ':/'); + + return $scheme; + } + + /** + * @param string $scheme + * @param string $host + * @param int $port + * + * @return int|null + * + * @throws \InvalidArgumentException If the port is invalid. + */ + private function filterPort($scheme, $host, $port) + { + if (null !== $port) { + $port = (int) $port; + if (1 > $port || 0xffff < $port) { + throw new \InvalidArgumentException( + sprintf('Invalid port: %d. Must be between 1 and 65535', $port) + ); + } + } + + return $this->isNonStandardPort($scheme, $host, $port) ? $port : null; + } + + /** + * Filters the path of a URI + * + * @param $path + * + * @return string + */ + private function filterPath($path) + { + return preg_replace_callback( + '/(?:[^' . self::$charUnreserved . self::$charSubDelims . ':@\/%]+|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $path + ); + } + + /** + * Filters the query string or fragment of a URI. + * + * @param $str + * + * @return string + */ + private function filterQueryAndFragment($str) + { + return preg_replace_callback( + '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $str + ); + } + + private function rawurlencodeMatchZero(array $match) + { + return rawurlencode($match[0]); + } +} diff --git a/Lib/Alisms/GuzzleHttp/Psr7/functions.php b/Lib/Alisms/GuzzleHttp/Psr7/functions.php new file mode 100644 index 0000000..fd3e7f5 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Psr7/functions.php @@ -0,0 +1,802 @@ +getMethod() . ' ' + . $message->getRequestTarget()) + . ' HTTP/' . $message->getProtocolVersion(); + if (!$message->hasHeader('host')) { + $msg .= "\r\nHost: " . $message->getUri()->getHost(); + } + } elseif ($message instanceof ResponseInterface) { + $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' + . $message->getStatusCode() . ' ' + . $message->getReasonPhrase(); + } else { + throw new \InvalidArgumentException('Unknown message type'); + } + + foreach ($message->getHeaders() as $name => $values) { + $msg .= "\r\n{$name}: " . implode(', ', $values); + } + + return "{$msg}\r\n\r\n" . $message->getBody(); +} + +/** + * Returns a UriInterface for the given value. + * + * This function accepts a string or {@see Psr\Http\Message\UriInterface} and + * returns a UriInterface for the given value. If the value is already a + * `UriInterface`, it is returned as-is. + * + * @param string|UriInterface $uri + * + * @return UriInterface + * @throws \InvalidArgumentException + */ +function uri_for($uri) +{ + if ($uri instanceof UriInterface) { + return $uri; + } elseif (is_string($uri)) { + return new Uri($uri); + } + + throw new \InvalidArgumentException('URI must be a string or UriInterface'); +} + +/** + * Create a new stream based on the input type. + * + * Options is an associative array that can contain the following keys: + * - metadata: Array of custom metadata. + * - size: Size of the stream. + * + * @param resource|string|StreamInterface $resource Entity body data + * @param array $options Additional options + * + * @return Stream + * @throws \InvalidArgumentException if the $resource arg is not valid. + */ +function stream_for($resource = '', array $options = []) +{ + switch (gettype($resource)) { + case 'string': + $stream = fopen('php://temp', 'r+'); + if ($resource !== '') { + fwrite($stream, $resource); + fseek($stream, 0); + } + return new Stream($stream, $options); + case 'resource': + return new Stream($resource, $options); + case 'object': + if ($resource instanceof StreamInterface) { + return $resource; + } elseif ($resource instanceof \Iterator) { + return new PumpStream(function () use ($resource) { + if (!$resource->valid()) { + return false; + } + $result = $resource->current(); + $resource->next(); + return $result; + }, $options); + } elseif (method_exists($resource, '__toString')) { + return stream_for((string) $resource, $options); + } + break; + case 'NULL': + return new Stream(fopen('php://temp', 'r+'), $options); + } + + if (is_callable($resource)) { + return new PumpStream($resource, $options); + } + + throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource)); +} + +/** + * Parse an array of header values containing ";" separated data into an + * array of associative arrays representing the header key value pair + * data of the header. When a parameter does not contain a value, but just + * contains a key, this function will inject a key with a '' string value. + * + * @param string|array $header Header to parse into components. + * + * @return array Returns the parsed header values. + */ +function parse_header($header) +{ + static $trimmed = "\"' \n\t\r"; + $params = $matches = []; + + foreach (normalize_header($header) as $val) { + $part = []; + foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) { + if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) { + $m = $matches[0]; + if (isset($m[1])) { + $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); + } else { + $part[] = trim($m[0], $trimmed); + } + } + } + if ($part) { + $params[] = $part; + } + } + + return $params; +} + +/** + * Converts an array of header values that may contain comma separated + * headers into an array of headers with no comma separated values. + * + * @param string|array $header Header to normalize. + * + * @return array Returns the normalized header field values. + */ +function normalize_header($header) +{ + if (!is_array($header)) { + return array_map('trim', explode(',', $header)); + } + + $result = []; + foreach ($header as $value) { + foreach ((array) $value as $v) { + if (strpos($v, ',') === false) { + $result[] = $v; + continue; + } + foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) { + $result[] = trim($vv); + } + } + } + + return $result; +} + +/** + * Clone and modify a request with the given changes. + * + * The changes can be one of: + * - method: (string) Changes the HTTP method. + * - set_headers: (array) Sets the given headers. + * - remove_headers: (array) Remove the given headers. + * - body: (mixed) Sets the given body. + * - uri: (UriInterface) Set the URI. + * - query: (string) Set the query string value of the URI. + * - version: (string) Set the protocol version. + * + * @param RequestInterface $request Request to clone and modify. + * @param array $changes Changes to apply. + * + * @return RequestInterface + */ +function modify_request(RequestInterface $request, array $changes) +{ + if (!$changes) { + return $request; + } + + $headers = $request->getHeaders(); + + if (!isset($changes['uri'])) { + $uri = $request->getUri(); + } else { + // Remove the host header if one is on the URI + if ($host = $changes['uri']->getHost()) { + $changes['set_headers']['Host'] = $host; + } + $uri = $changes['uri']; + } + + if (!empty($changes['remove_headers'])) { + $headers = _caseless_remove($changes['remove_headers'], $headers); + } + + if (!empty($changes['set_headers'])) { + $headers = _caseless_remove(array_keys($changes['set_headers']), $headers); + $headers = $changes['set_headers'] + $headers; + } + + if (isset($changes['query'])) { + $uri = $uri->withQuery($changes['query']); + } + + return new Request( + isset($changes['method']) ? $changes['method'] : $request->getMethod(), + $uri, + $headers, + isset($changes['body']) ? $changes['body'] : $request->getBody(), + isset($changes['version']) + ? $changes['version'] + : $request->getProtocolVersion() + ); +} + +/** + * Attempts to rewind a message body and throws an exception on failure. + * + * The body of the message will only be rewound if a call to `tell()` returns a + * value other than `0`. + * + * @param MessageInterface $message Message to rewind + * + * @throws \RuntimeException + */ +function rewind_body(MessageInterface $message) +{ + $body = $message->getBody(); + + if ($body->tell()) { + $body->rewind(); + } +} + +/** + * Safely opens a PHP stream resource using a filename. + * + * When fopen fails, PHP normally raises a warning. This function adds an + * error handler that checks for errors and throws an exception instead. + * + * @param string $filename File to open + * @param string $mode Mode used to open the file + * + * @return resource + * @throws \RuntimeException if the file cannot be opened + */ +function try_fopen($filename, $mode) +{ + $ex = null; + set_error_handler(function () use ($filename, $mode, &$ex) { + $ex = new \RuntimeException(sprintf( + 'Unable to open %s using mode %s: %s', + $filename, + $mode, + func_get_args()[1] + )); + }); + + $handle = fopen($filename, $mode); + restore_error_handler(); + + if ($ex) { + /** @var $ex \RuntimeException */ + throw $ex; + } + + return $handle; +} + +/** + * Copy the contents of a stream into a string until the given number of + * bytes have been read. + * + * @param StreamInterface $stream Stream to read + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * @return string + * @throws \RuntimeException on error. + */ +function copy_to_string(StreamInterface $stream, $maxLen = -1) +{ + $buffer = ''; + + if ($maxLen === -1) { + while (!$stream->eof()) { + $buf = $stream->read(1048576); + // Using a loose equality here to match on '' and false. + if ($buf == null) { + break; + } + $buffer .= $buf; + } + return $buffer; + } + + $len = 0; + while (!$stream->eof() && $len < $maxLen) { + $buf = $stream->read($maxLen - $len); + // Using a loose equality here to match on '' and false. + if ($buf == null) { + break; + } + $buffer .= $buf; + $len = strlen($buffer); + } + + return $buffer; +} + +/** + * Copy the contents of a stream into another stream until the given number + * of bytes have been read. + * + * @param StreamInterface $source Stream to read from + * @param StreamInterface $dest Stream to write to + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ +function copy_to_stream( + StreamInterface $source, + StreamInterface $dest, + $maxLen = -1 +) { + if ($maxLen === -1) { + while (!$source->eof()) { + if (!$dest->write($source->read(1048576))) { + break; + } + } + return; + } + + $bytes = 0; + while (!$source->eof()) { + $buf = $source->read($maxLen - $bytes); + if (!($len = strlen($buf))) { + break; + } + $bytes += $len; + $dest->write($buf); + if ($bytes == $maxLen) { + break; + } + } +} + +/** + * Calculate a hash of a Stream + * + * @param StreamInterface $stream Stream to calculate the hash for + * @param string $algo Hash algorithm (e.g. md5, crc32, etc) + * @param bool $rawOutput Whether or not to use raw output + * + * @return string Returns the hash of the stream + * @throws \RuntimeException on error. + */ +function hash( + StreamInterface $stream, + $algo, + $rawOutput = false +) { + $pos = $stream->tell(); + + if ($pos > 0) { + $stream->rewind(); + } + + $ctx = hash_init($algo); + while (!$stream->eof()) { + hash_update($ctx, $stream->read(1048576)); + } + + $out = hash_final($ctx, (bool) $rawOutput); + $stream->seek($pos); + + return $out; +} + +/** + * Read a line from the stream up to the maximum allowed buffer length + * + * @param StreamInterface $stream Stream to read from + * @param int $maxLength Maximum buffer length + * + * @return string|bool + */ +function readline(StreamInterface $stream, $maxLength = null) +{ + $buffer = ''; + $size = 0; + + while (!$stream->eof()) { + // Using a loose equality here to match on '' and false. + if (null == ($byte = $stream->read(1))) { + return $buffer; + } + $buffer .= $byte; + // Break when a new line is found or the max length - 1 is reached + if ($byte == PHP_EOL || ++$size == $maxLength - 1) { + break; + } + } + + return $buffer; +} + +/** + * Parses a request message string into a request object. + * + * @param string $message Request message string. + * + * @return Request + */ +function parse_request($message) +{ + $data = _parse_message($message); + $matches = []; + if (!preg_match('/^[a-zA-Z]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) { + throw new \InvalidArgumentException('Invalid request string'); + } + $parts = explode(' ', $data['start-line'], 3); + $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1'; + + $request = new Request( + $parts[0], + $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1], + $data['headers'], + $data['body'], + $version + ); + + return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]); +} + +/** + * Parses a response message string into a response object. + * + * @param string $message Response message string. + * + * @return Response + */ +function parse_response($message) +{ + $data = _parse_message($message); + if (!preg_match('/^HTTP\/.* [0-9]{3} .*/', $data['start-line'])) { + throw new \InvalidArgumentException('Invalid response string'); + } + $parts = explode(' ', $data['start-line'], 3); + + return new Response( + $parts[1], + $data['headers'], + $data['body'], + explode('/', $parts[0])[1], + isset($parts[2]) ? $parts[2] : null + ); +} + +/** + * Parse a query string into an associative array. + * + * If multiple values are found for the same key, the value of that key + * value pair will become an array. This function does not parse nested + * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will + * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']). + * + * @param string $str Query string to parse + * @param bool|string $urlEncoding How the query string is encoded + * + * @return array + */ +function parse_query($str, $urlEncoding = true) +{ + $result = []; + + if ($str === '') { + return $result; + } + + if ($urlEncoding === true) { + $decoder = function ($value) { + return rawurldecode(str_replace('+', ' ', $value)); + }; + } elseif ($urlEncoding == PHP_QUERY_RFC3986) { + $decoder = 'rawurldecode'; + } elseif ($urlEncoding == PHP_QUERY_RFC1738) { + $decoder = 'urldecode'; + } else { + $decoder = function ($str) { return $str; }; + } + + foreach (explode('&', $str) as $kvp) { + $parts = explode('=', $kvp, 2); + $key = $decoder($parts[0]); + $value = isset($parts[1]) ? $decoder($parts[1]) : null; + if (!isset($result[$key])) { + $result[$key] = $value; + } else { + if (!is_array($result[$key])) { + $result[$key] = [$result[$key]]; + } + $result[$key][] = $value; + } + } + + return $result; +} + +/** + * Build a query string from an array of key value pairs. + * + * This function can use the return value of parseQuery() to build a query + * string. This function does not modify the provided keys when an array is + * encountered (like http_build_query would). + * + * @param array $params Query string parameters. + * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986 + * to encode using RFC3986, or PHP_QUERY_RFC1738 + * to encode using RFC1738. + * @return string + */ +function build_query(array $params, $encoding = PHP_QUERY_RFC3986) +{ + if (!$params) { + return ''; + } + + if ($encoding === false) { + $encoder = function ($str) { return $str; }; + } elseif ($encoding == PHP_QUERY_RFC3986) { + $encoder = 'rawurlencode'; + } elseif ($encoding == PHP_QUERY_RFC1738) { + $encoder = 'urlencode'; + } else { + throw new \InvalidArgumentException('Invalid type'); + } + + $qs = ''; + foreach ($params as $k => $v) { + $k = $encoder($k); + if (!is_array($v)) { + $qs .= $k; + if ($v !== null) { + $qs .= '=' . $encoder($v); + } + $qs .= '&'; + } else { + foreach ($v as $vv) { + $qs .= $k; + if ($vv !== null) { + $qs .= '=' . $encoder($vv); + } + $qs .= '&'; + } + } + } + + return $qs ? (string) substr($qs, 0, -1) : ''; +} + +/** + * Determines the mimetype of a file by looking at its extension. + * + * @param $filename + * + * @return null|string + */ +function mimetype_from_filename($filename) +{ + return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION)); +} + +/** + * Maps a file extensions to a mimetype. + * + * @param $extension string The file extension. + * + * @return string|null + * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types + */ +function mimetype_from_extension($extension) +{ + static $mimetypes = [ + '7z' => 'application/x-7z-compressed', + 'aac' => 'audio/x-aac', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'asc' => 'text/plain', + 'asf' => 'video/x-ms-asf', + 'atom' => 'application/atom+xml', + 'avi' => 'video/x-msvideo', + 'bmp' => 'image/bmp', + 'bz2' => 'application/x-bzip2', + 'cer' => 'application/pkix-cert', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'css' => 'text/css', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'deb' => 'application/x-debian-package', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dvi' => 'application/x-dvi', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'etx' => 'text/x-setext', + 'flac' => 'audio/flac', + 'flv' => 'video/x-flv', + 'gif' => 'image/gif', + 'gz' => 'application/gzip', + 'htm' => 'text/html', + 'html' => 'text/html', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ini' => 'text/plain', + 'iso' => 'application/x-iso9660-image', + 'jar' => 'application/java-archive', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'js' => 'text/javascript', + 'json' => 'application/json', + 'latex' => 'application/x-latex', + 'log' => 'text/plain', + 'm4a' => 'audio/mp4', + 'm4v' => 'video/mp4', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mov' => 'video/quicktime', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4v' => 'video/mp4', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'oga' => 'audio/ogg', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'pbm' => 'image/x-portable-bitmap', + 'pdf' => 'application/pdf', + 'pgm' => 'image/x-portable-graymap', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'ppm' => 'image/x-portable-pixmap', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'ps' => 'application/postscript', + 'qt' => 'video/quicktime', + 'rar' => 'application/x-rar-compressed', + 'ras' => 'image/x-cmu-raster', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'svg' => 'image/svg+xml', + 'swf' => 'application/x-shockwave-flash', + 'tar' => 'application/x-tar', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'torrent' => 'application/x-bittorrent', + 'ttf' => 'application/x-font-ttf', + 'txt' => 'text/plain', + 'wav' => 'audio/x-wav', + 'webm' => 'video/webm', + 'wma' => 'audio/x-ms-wma', + 'wmv' => 'video/x-ms-wmv', + 'woff' => 'application/x-font-woff', + 'wsdl' => 'application/wsdl+xml', + 'xbm' => 'image/x-xbitmap', + 'xls' => 'application/vnd.ms-excel', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xml' => 'application/xml', + 'xpm' => 'image/x-xpixmap', + 'xwd' => 'image/x-xwindowdump', + 'yaml' => 'text/yaml', + 'yml' => 'text/yaml', + 'zip' => 'application/zip', + ]; + + $extension = strtolower($extension); + + return isset($mimetypes[$extension]) + ? $mimetypes[$extension] + : null; +} + +/** + * Parses an HTTP message into an associative array. + * + * The array contains the "start-line" key containing the start line of + * the message, "headers" key containing an associative array of header + * array values, and a "body" key containing the body of the message. + * + * @param string $message HTTP request or response to parse. + * + * @return array + * @internal + */ +function _parse_message($message) +{ + if (!$message) { + throw new \InvalidArgumentException('Invalid message'); + } + + // Iterate over each line in the message, accounting for line endings + $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE); + $result = ['start-line' => array_shift($lines), 'headers' => [], 'body' => '']; + array_shift($lines); + + for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) { + $line = $lines[$i]; + // If two line breaks were encountered, then this is the end of body + if (empty($line)) { + if ($i < $totalLines - 1) { + $result['body'] = implode('', array_slice($lines, $i + 2)); + } + break; + } + if (strpos($line, ':')) { + $parts = explode(':', $line, 2); + $key = trim($parts[0]); + $value = isset($parts[1]) ? trim($parts[1]) : ''; + $result['headers'][$key][] = $value; + } + } + + return $result; +} + +/** + * Constructs a URI for an HTTP request message. + * + * @param string $path Path from the start-line + * @param array $headers Array of headers (each value an array). + * + * @return string + * @internal + */ +function _parse_request_uri($path, array $headers) +{ + $hostKey = array_filter(array_keys($headers), function ($k) { + return strtolower($k) === 'host'; + }); + + // If no host is found, then a full URI cannot be constructed. + if (!$hostKey) { + return $path; + } + + $host = $headers[reset($hostKey)][0]; + $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; + + return $scheme . '://' . $host . '/' . ltrim($path, '/'); +} + +/** @internal */ +function _caseless_remove($keys, array $data) +{ + $result = []; + + foreach ($keys as &$key) { + $key = strtolower($key); + } + + foreach ($data as $k => $v) { + if (!in_array(strtolower($k), $keys)) { + $result[$k] = $v; + } + } + + return $result; +} diff --git a/Lib/Alisms/GuzzleHttp/Psr7/functions_include.php b/Lib/Alisms/GuzzleHttp/Psr7/functions_include.php new file mode 100644 index 0000000..96a4a83 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/Psr7/functions_include.php @@ -0,0 +1,6 @@ + 5, + 'protocols' => ['http', 'https'], + 'strict' => false, + 'referer' => false, + 'track_redirects' => false, + ]; + + /** @var callable */ + private $nextHandler; + + /** + * @param callable $nextHandler Next handler to invoke. + */ + public function __construct(callable $nextHandler) + { + $this->nextHandler = $nextHandler; + } + + /** + * @param RequestInterface $request + * @param array $options + * + * @return PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + $fn = $this->nextHandler; + + if (empty($options['allow_redirects'])) { + return $fn($request, $options); + } + + if ($options['allow_redirects'] === true) { + $options['allow_redirects'] = self::$defaultSettings; + } elseif (!is_array($options['allow_redirects'])) { + throw new \InvalidArgumentException('allow_redirects must be true, false, or array'); + } else { + // Merge the default settings with the provided settings + $options['allow_redirects'] += self::$defaultSettings; + } + + if (empty($options['allow_redirects']['max'])) { + return $fn($request, $options); + } + + return $fn($request, $options) + ->then(function (ResponseInterface $response) use ($request, $options) { + return $this->checkRedirect($request, $options, $response); + }); + } + + /** + * @param RequestInterface $request + * @param array $options + * @param ResponseInterface|PromiseInterface $response + * + * @return ResponseInterface|PromiseInterface + */ + public function checkRedirect( + RequestInterface $request, + array $options, + ResponseInterface $response + ) { + if (substr($response->getStatusCode(), 0, 1) != '3' + || !$response->hasHeader('Location') + ) { + return $response; + } + + $this->guardMax($request, $options); + $nextRequest = $this->modifyRequest($request, $options, $response); + + if (isset($options['allow_redirects']['on_redirect'])) { + call_user_func( + $options['allow_redirects']['on_redirect'], + $request, + $response, + $nextRequest->getUri() + ); + } + + /** @var PromiseInterface|ResponseInterface $promise */ + $promise = $this($nextRequest, $options); + + // Add headers to be able to track history of redirects. + if (!empty($options['allow_redirects']['track_redirects'])) { + return $this->withTracking( + $promise, + (string) $nextRequest->getUri() + ); + } + + return $promise; + } + + private function withTracking(PromiseInterface $promise, $uri) + { + return $promise->then( + function (ResponseInterface $response) use ($uri) { + // Note that we are pushing to the front of the list as this + // would be an earlier response than what is currently present + // in the history header. + $header = $response->getHeader(self::HISTORY_HEADER); + array_unshift($header, $uri); + return $response->withHeader(self::HISTORY_HEADER, $header); + } + ); + } + + private function guardMax(RequestInterface $request, array &$options) + { + $current = isset($options['__redirect_count']) + ? $options['__redirect_count'] + : 0; + $options['__redirect_count'] = $current + 1; + $max = $options['allow_redirects']['max']; + + if ($options['__redirect_count'] > $max) { + throw new TooManyRedirectsException( + "Will not follow more than {$max} redirects", + $request + ); + } + } + + /** + * @param RequestInterface $request + * @param array $options + * @param ResponseInterface $response + * + * @return RequestInterface + */ + public function modifyRequest( + RequestInterface $request, + array $options, + ResponseInterface $response + ) { + // Request modifications to apply. + $modify = []; + $protocols = $options['allow_redirects']['protocols']; + + // Use a GET request if this is an entity enclosing request and we are + // not forcing RFC compliance, but rather emulating what all browsers + // would do. + $statusCode = $response->getStatusCode(); + if ($statusCode == 303 || + ($statusCode <= 302 && $request->getBody() && !$options['allow_redirects']['strict']) + ) { + $modify['method'] = 'GET'; + $modify['body'] = ''; + } + + $modify['uri'] = $this->redirectUri($request, $response, $protocols); + Psr7\rewind_body($request); + + // Add the Referer header if it is told to do so and only + // add the header if we are not redirecting from https to http. + if ($options['allow_redirects']['referer'] + && $modify['uri']->getScheme() === $request->getUri()->getScheme() + ) { + $uri = $request->getUri()->withUserInfo('', ''); + $modify['set_headers']['Referer'] = (string) $uri; + } else { + $modify['remove_headers'][] = 'Referer'; + } + + // Remove Authorization header if host is different. + if ($request->getUri()->getHost() !== $modify['uri']->getHost()) { + $modify['remove_headers'][] = 'Authorization'; + } + + return Psr7\modify_request($request, $modify); + } + + /** + * Set the appropriate URL on the request based on the location header + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param array $protocols + * + * @return UriInterface + */ + private function redirectUri( + RequestInterface $request, + ResponseInterface $response, + array $protocols + ) { + $location = Psr7\Uri::resolve( + $request->getUri(), + $response->getHeaderLine('Location') + ); + + // Ensure that the redirect URI is allowed based on the protocols. + if (!in_array($location->getScheme(), $protocols)) { + throw new BadResponseException( + sprintf( + 'Redirect URI, %s, does not use one of the allowed redirect protocols: %s', + $location, + implode(', ', $protocols) + ), + $request, + $response + ); + } + + return $location; + } +} diff --git a/Lib/Alisms/GuzzleHttp/RequestOptions.php b/Lib/Alisms/GuzzleHttp/RequestOptions.php new file mode 100644 index 0000000..3af2f36 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/RequestOptions.php @@ -0,0 +1,244 @@ +decider = $decider; + $this->nextHandler = $nextHandler; + $this->delay = $delay ?: __CLASS__ . '::exponentialDelay'; + } + + /** + * Default exponential backoff delay function. + * + * @param $retries + * + * @return int + */ + public static function exponentialDelay($retries) + { + return (int) pow(2, $retries - 1); + } + + /** + * @param RequestInterface $request + * @param array $options + * + * @return PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + if (!isset($options['retries'])) { + $options['retries'] = 0; + } + + $fn = $this->nextHandler; + return $fn($request, $options) + ->then( + $this->onFulfilled($request, $options), + $this->onRejected($request, $options) + ); + } + + private function onFulfilled(RequestInterface $req, array $options) + { + return function ($value) use ($req, $options) { + if (!call_user_func( + $this->decider, + $options['retries'], + $req, + $value, + null + )) { + return $value; + } + return $this->doRetry($req, $options); + }; + } + + private function onRejected(RequestInterface $req, array $options) + { + return function ($reason) use ($req, $options) { + if (!call_user_func( + $this->decider, + $options['retries'], + $req, + null, + $reason + )) { + return new RejectedPromise($reason); + } + return $this->doRetry($req, $options); + }; + } + + private function doRetry(RequestInterface $request, array $options) + { + $options['delay'] = call_user_func($this->delay, ++$options['retries']); + + return $this($request, $options); + } +} diff --git a/Lib/Alisms/GuzzleHttp/TransferStats.php b/Lib/Alisms/GuzzleHttp/TransferStats.php new file mode 100644 index 0000000..15f717e --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/TransferStats.php @@ -0,0 +1,126 @@ +request = $request; + $this->response = $response; + $this->transferTime = $transferTime; + $this->handlerErrorData = $handlerErrorData; + $this->handlerStats = $handlerStats; + } + + /** + * @return RequestInterface + */ + public function getRequest() + { + return $this->request; + } + + /** + * Returns the response that was received (if any). + * + * @return ResponseInterface|null + */ + public function getResponse() + { + return $this->response; + } + + /** + * Returns true if a response was received. + * + * @return bool + */ + public function hasResponse() + { + return $this->response !== null; + } + + /** + * Gets handler specific error data. + * + * This might be an exception, a integer representing an error code, or + * anything else. Relying on this value assumes that you know what handler + * you are using. + * + * @return mixed + */ + public function getHandlerErrorData() + { + return $this->handlerErrorData; + } + + /** + * Get the effective URI the request was sent to. + * + * @return UriInterface + */ + public function getEffectiveUri() + { + return $this->request->getUri(); + } + + /** + * Get the estimated time the request was being transferred by the handler. + * + * @return float Time in seconds. + */ + public function getTransferTime() + { + return $this->transferTime; + } + + /** + * Gets an array of all of the handler specific transfer data. + * + * @return array + */ + public function getHandlerStats() + { + return $this->handlerStats; + } + + /** + * Get a specific handler statistic from the handler by name. + * + * @param string $stat Handler specific transfer stat to retrieve. + * + * @return mixed|null + */ + public function getHandlerStat($stat) + { + return isset($this->handlerStats[$stat]) + ? $this->handlerStats[$stat] + : null; + } +} diff --git a/Lib/Alisms/GuzzleHttp/UriTemplate.php b/Lib/Alisms/GuzzleHttp/UriTemplate.php new file mode 100644 index 0000000..55dfeb5 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/UriTemplate.php @@ -0,0 +1,241 @@ + array('prefix' => '', 'joiner' => ',', 'query' => false), + '+' => array('prefix' => '', 'joiner' => ',', 'query' => false), + '#' => array('prefix' => '#', 'joiner' => ',', 'query' => false), + '.' => array('prefix' => '.', 'joiner' => '.', 'query' => false), + '/' => array('prefix' => '/', 'joiner' => '/', 'query' => false), + ';' => array('prefix' => ';', 'joiner' => ';', 'query' => true), + '?' => array('prefix' => '?', 'joiner' => '&', 'query' => true), + '&' => array('prefix' => '&', 'joiner' => '&', 'query' => true) + ); + + /** @var array Delimiters */ + private static $delims = array(':', '/', '?', '#', '[', ']', '@', '!', '$', + '&', '\'', '(', ')', '*', '+', ',', ';', '='); + + /** @var array Percent encoded delimiters */ + private static $delimsPct = array('%3A', '%2F', '%3F', '%23', '%5B', '%5D', + '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', + '%3B', '%3D'); + + public function expand($template, array $variables) + { + if (false === strpos($template, '{')) { + return $template; + } + + $this->template = $template; + $this->variables = $variables; + + return preg_replace_callback( + '/\{([^\}]+)\}/', + [$this, 'expandMatch'], + $this->template + ); + } + + /** + * Parse an expression into parts + * + * @param string $expression Expression to parse + * + * @return array Returns an associative array of parts + */ + private function parseExpression($expression) + { + $result = array(); + + if (isset(self::$operatorHash[$expression[0]])) { + $result['operator'] = $expression[0]; + $expression = substr($expression, 1); + } else { + $result['operator'] = ''; + } + + foreach (explode(',', $expression) as $value) { + $value = trim($value); + $varspec = array(); + if ($colonPos = strpos($value, ':')) { + $varspec['value'] = substr($value, 0, $colonPos); + $varspec['modifier'] = ':'; + $varspec['position'] = (int) substr($value, $colonPos + 1); + } elseif (substr($value, -1) == '*') { + $varspec['modifier'] = '*'; + $varspec['value'] = substr($value, 0, -1); + } else { + $varspec['value'] = (string) $value; + $varspec['modifier'] = ''; + } + $result['values'][] = $varspec; + } + + return $result; + } + + /** + * Process an expansion + * + * @param array $matches Matches met in the preg_replace_callback + * + * @return string Returns the replacement string + */ + private function expandMatch(array $matches) + { + static $rfc1738to3986 = array('+' => '%20', '%7e' => '~'); + + $replacements = array(); + $parsed = self::parseExpression($matches[1]); + $prefix = self::$operatorHash[$parsed['operator']]['prefix']; + $joiner = self::$operatorHash[$parsed['operator']]['joiner']; + $useQuery = self::$operatorHash[$parsed['operator']]['query']; + + foreach ($parsed['values'] as $value) { + + if (!isset($this->variables[$value['value']])) { + continue; + } + + $variable = $this->variables[$value['value']]; + $actuallyUseQuery = $useQuery; + $expanded = ''; + + if (is_array($variable)) { + + $isAssoc = $this->isAssoc($variable); + $kvp = array(); + foreach ($variable as $key => $var) { + + if ($isAssoc) { + $key = rawurlencode($key); + $isNestedArray = is_array($var); + } else { + $isNestedArray = false; + } + + if (!$isNestedArray) { + $var = rawurlencode($var); + if ($parsed['operator'] == '+' || + $parsed['operator'] == '#' + ) { + $var = $this->decodeReserved($var); + } + } + + if ($value['modifier'] == '*') { + if ($isAssoc) { + if ($isNestedArray) { + // Nested arrays must allow for deeply nested + // structures. + $var = strtr( + http_build_query([$key => $var]), + $rfc1738to3986 + ); + } else { + $var = $key . '=' . $var; + } + } elseif ($key > 0 && $actuallyUseQuery) { + $var = $value['value'] . '=' . $var; + } + } + + $kvp[$key] = $var; + } + + if (empty($variable)) { + $actuallyUseQuery = false; + } elseif ($value['modifier'] == '*') { + $expanded = implode($joiner, $kvp); + if ($isAssoc) { + // Don't prepend the value name when using the explode + // modifier with an associative array. + $actuallyUseQuery = false; + } + } else { + if ($isAssoc) { + // When an associative array is encountered and the + // explode modifier is not set, then the result must be + // a comma separated list of keys followed by their + // respective values. + foreach ($kvp as $k => &$v) { + $v = $k . ',' . $v; + } + } + $expanded = implode(',', $kvp); + } + + } else { + if ($value['modifier'] == ':') { + $variable = substr($variable, 0, $value['position']); + } + $expanded = rawurlencode($variable); + if ($parsed['operator'] == '+' || $parsed['operator'] == '#') { + $expanded = $this->decodeReserved($expanded); + } + } + + if ($actuallyUseQuery) { + if (!$expanded && $joiner != '&') { + $expanded = $value['value']; + } else { + $expanded = $value['value'] . '=' . $expanded; + } + } + + $replacements[] = $expanded; + } + + $ret = implode($joiner, $replacements); + if ($ret && $prefix) { + return $prefix . $ret; + } + + return $ret; + } + + /** + * Determines if an array is associative. + * + * This makes the assumption that input arrays are sequences or hashes. + * This assumption is a tradeoff for accuracy in favor of speed, but it + * should work in almost every case where input is supplied for a URI + * template. + * + * @param array $array Array to check + * + * @return bool + */ + private function isAssoc(array $array) + { + return $array && array_keys($array)[0] !== 0; + } + + /** + * Removes percent encoding on reserved characters (used with + and # + * modifiers). + * + * @param string $string String to fix + * + * @return string + */ + private function decodeReserved($string) + { + return str_replace(self::$delimsPct, self::$delims, $string); + } +} diff --git a/Lib/Alisms/GuzzleHttp/functions.php b/Lib/Alisms/GuzzleHttp/functions.php new file mode 100644 index 0000000..d0a2ca8 --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/functions.php @@ -0,0 +1,280 @@ +expand($template, $variables); +} + +/** + * Debug function used to describe the provided value type and class. + * + * @param mixed $input + * + * @return string Returns a string containing the type of the variable and + * if a class is provided, the class name. + */ +function describe_type($input) +{ + switch (gettype($input)) { + case 'object': + return 'object(' . get_class($input) . ')'; + case 'array': + return 'array(' . count($input) . ')'; + default: + ob_start(); + var_dump($input); + // normalize float vs double + return str_replace('double(', 'float(', rtrim(ob_get_clean())); + } +} + +/** + * Parses an array of header lines into an associative array of headers. + * + * @param array $lines Header lines array of strings in the following + * format: "Name: Value" + * @return array + */ +function headers_from_lines($lines) +{ + $headers = []; + + foreach ($lines as $line) { + $parts = explode(':', $line, 2); + $headers[trim($parts[0])][] = isset($parts[1]) + ? trim($parts[1]) + : null; + } + + return $headers; +} + +/** + * Returns a debug stream based on the provided variable. + * + * @param mixed $value Optional value + * + * @return resource + */ +function debug_resource($value = null) +{ + if (is_resource($value)) { + return $value; + } elseif (defined('STDOUT')) { + return STDOUT; + } + + return fopen('php://output', 'w'); +} + +/** + * Chooses and creates a default handler to use based on the environment. + * + * The returned handler is not wrapped by any default middlewares. + * + * @throws \RuntimeException if no viable Handler is available. + * @return callable Returns the best handler for the given system. + */ +function choose_handler() +{ + $handler = null; + if (extension_loaded('curl')) { + $handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler()); + } + + if (ini_get('allow_url_fopen')) { + $handler = $handler + ? Proxy::wrapStreaming($handler, new StreamHandler()) + : new StreamHandler(); + } elseif (!$handler) { + throw new \RuntimeException('GuzzleHttp requires cURL, the ' + . 'allow_url_fopen ini setting, or a custom HTTP handler.'); + } + + return $handler; +} + +/** + * Get the default User-Agent string to use with Guzzle + * + * @return string + */ +function default_user_agent() +{ + static $defaultAgent = ''; + + if (!$defaultAgent) { + $defaultAgent = 'GuzzleHttp/' . Client::VERSION; + if (extension_loaded('curl') && function_exists('curl_version')) { + $defaultAgent .= ' curl/' . \curl_version()['version']; + } + $defaultAgent .= ' PHP/' . PHP_VERSION; + } + + return $defaultAgent; +} + +/** + * Returns the default cacert bundle for the current system. + * + * First, the openssl.cafile and curl.cainfo php.ini settings are checked. + * If those settings are not configured, then the common locations for + * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X + * and Windows are checked. If any of these file locations are found on + * disk, they will be utilized. + * + * Note: the result of this function is cached for subsequent calls. + * + * @return string + * @throws \RuntimeException if no bundle can be found. + */ +function default_ca_bundle() +{ + static $cached = null; + static $cafiles = [ + // Red Hat, CentOS, Fedora (provided by the ca-certificates package) + '/etc/pki/tls/certs/ca-bundle.crt', + // Ubuntu, Debian (provided by the ca-certificates package) + '/etc/ssl/certs/ca-certificates.crt', + // FreeBSD (provided by the ca_root_nss package) + '/usr/local/share/certs/ca-root-nss.crt', + // OS X provided by homebrew (using the default path) + '/usr/local/etc/openssl/cert.pem', + // Google app engine + '/etc/ca-certificates.crt', + // Windows? + 'C:\\windows\\system32\\curl-ca-bundle.crt', + 'C:\\windows\\curl-ca-bundle.crt', + ]; + + if ($cached) { + return $cached; + } + + if ($ca = ini_get('openssl.cafile')) { + return $cached = $ca; + } + + if ($ca = ini_get('curl.cainfo')) { + return $cached = $ca; + } + + foreach ($cafiles as $filename) { + if (file_exists($filename)) { + return $cached = $filename; + } + } + + throw new \RuntimeException(<<< EOT +No system CA bundle could be found in any of the the common system locations. +PHP versions earlier than 5.6 are not properly configured to use the system's +CA bundle by default. In order to verify peer certificates, you will need to +supply the path on disk to a certificate bundle to the 'verify' request +option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not +need a specific certificate bundle, then Mozilla provides a commonly used CA +bundle which can be downloaded here (provided by the maintainer of cURL): +https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt. Once +you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP +ini setting to point to the path to the file, allowing you to omit the 'verify' +request option. See http://curl.haxx.se/docs/sslcerts.html for more +information. +EOT + ); +} + +/** + * Creates an associative array of lowercase header names to the actual + * header casing. + * + * @param array $headers + * + * @return array + */ +function normalize_header_keys(array $headers) +{ + $result = []; + foreach (array_keys($headers) as $key) { + $result[strtolower($key)] = $key; + } + + return $result; +} + +/** + * Returns true if the provided host matches any of the no proxy areas. + * + * This method will strip a port from the host if it is present. Each pattern + * can be matched with an exact match (e.g., "foo.com" == "foo.com") or a + * partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" == + * "baz.foo.com", but ".foo.com" != "foo.com"). + * + * Areas are matched in the following cases: + * 1. "*" (without quotes) always matches any hosts. + * 2. An exact match. + * 3. The area starts with "." and the area is the last part of the host. e.g. + * '.mit.edu' will match any host that ends with '.mit.edu'. + * + * @param string $host Host to check against the patterns. + * @param array $noProxyArray An array of host patterns. + * + * @return bool + */ +function is_host_in_noproxy($host, array $noProxyArray) +{ + if (strlen($host) === 0) { + throw new \InvalidArgumentException('Empty host provided'); + } + + // Strip port if present. + if (strpos($host, ':')) { + $host = explode($host, ':', 2)[0]; + } + + foreach ($noProxyArray as $area) { + // Always match on wildcards. + if ($area === '*') { + return true; + } elseif (empty($area)) { + // Don't match on empty values. + continue; + } elseif ($area === $host) { + // Exact matches. + return true; + } else { + // Special match if the area when prefixed with ".". Remove any + // existing leading "." and add a new leading ".". + $area = '.' . ltrim($area, '.'); + if (substr($host, -(strlen($area))) === $area) { + return true; + } + } + } + + return false; +} diff --git a/Lib/Alisms/GuzzleHttp/functions_include.php b/Lib/Alisms/GuzzleHttp/functions_include.php new file mode 100644 index 0000000..a93393a --- /dev/null +++ b/Lib/Alisms/GuzzleHttp/functions_include.php @@ -0,0 +1,6 @@ +client = new Client($conf['end_point'], $conf['access_id'], $conf['access_key']); + /** + * Step 2. 获取主题引用 + */ + $topic = $this->client->getTopicRef($conf['topic_name']); + /** + * Step 3. 生成SMS消息属性 + */ + // 3.1 设置发送短信的签名(SMSSignName)和模板(SMSTemplateCode) + $batchSmsAttributes = new BatchSmsAttributes($conf['sign'], $conf['template']); + // 3.2 (如果在短信模板中定义了参数)指定短信模板中对应参数的值 + $to = explode(',', $to); + foreach ($to as $phone) { + var_dump($phone); + $batchSmsAttributes->addReceiver($phone, $param); + } + $messageAttributes = new MessageAttributes(array($batchSmsAttributes)); + /** + * Step 4. 设置SMS消息体(必须) + * + * 注:目前暂时不支持消息内容为空,需要指定消息内容,不为空即可。 + */ + $messageBody = $conf['message_body']; + /** + * Step 5. 发布SMS消息 + */ + $request = new PublishMessageRequest($messageBody, $messageAttributes); + + try { + return $topic->publishMessage($request); + } catch (MnsException $e) { + return $e; + } + + } + + /** + * 创建短信主题 + * @param $topicName + * @return bool + */ + protected function createTopic($topicName) { + $request = new CreateTopicRequest($topicName); + try { + + $res = $this->client->createTopic($request); + echo "TopicCreated! \n"; + return true; + } catch (MnsException $e) { + // 2. 可能因为网络错误,或者Topic已经存在等原因导致CreateTopic失败,这里CatchException并做对应的处理 + echo "CreateTopicFailed: " . $e . "\n"; + echo "MNSErrorCode: " . $e->getMnsErrorCode() . "\n"; + return false; + } + } + + /** + * 删除短信主题 + * @param $topicName + * @return bool + */ + protected function deleteTopic($topicName) { + try { + $this->client->deleteTopic($topicName); + echo "DeleteTopic Succeed! \n"; + return true; + } catch (MnsException $e) { + echo "DeleteTopic Failed: " . $e; + return false; + } + } + + /** + * 创建订阅 + * @param $subscriptionName + * @return bool + */ + protected function subscript($subscriptionName) { + // 1. 生成SubscriptionAttributes,这里第二个参数是Subscription的Endpoint。 + // 1.1 这里设置的是刚才启动的http server的地址 + // 1.2 更多支持的Endpoint类型可以参考:help.aliyun.com/document_detail/27479.html + $attributes = new SubscriptionAttributes($subscriptionName, 'http://' . $this->ip . ':' . $this->port); + try { + $this->topic->subscribe($attributes); + // 2. 订阅成功 + echo "Subscribed! \n"; + return true; + } catch (MnsException $e) { + // 3. 可能因为网络错误,或者同名的Subscription已存在等原因导致订阅出错,这里CatchException并做对应的处理 + echo "SubscribeFailed: " . $e . "\n"; + echo "MNSErrorCode: " . $e->getMnsErrorCode() . "\n"; + return false; + } + } + + /** + * 删除订阅 + * @param $subscriptionName + * @return bool + */ + protected function deleteSubscript($subscriptionName) { + try { + $this->topic->unsubscribe($subscriptionName); + echo "Unsubscribe Succeed! \n"; + return true; + } catch (MnsException $e) { + echo "Unsubscribe Failed: " . $e; + return false; + } + } + + /** + * 推送消息 + * @return bool + */ + protected function pushMessage() { + $messageBody = "test"; + // 1. 生成PublishMessageRequest + // 1.1 如果是推送到邮箱,还需要设置MessageAttributes,可以参照Tests/TopicTest.php里面的testPublishMailMessage + $request = new PublishMessageRequest($messageBody); + try { + $res = $this->topic->publishMessage($request); + // 2. PublishMessage成功 + echo "MessagePublished! \n"; + return true; + } catch (MnsException $e) { + // 3. 可能因为网络错误等原因导致PublishMessage失败,这里CatchException并做对应处理 + echo "PublishMessage Failed: " . $e . "\n"; + echo "MNSErrorCode: " . $e->getMnsErrorCode() . "\n"; + return false; + } + } +} \ No newline at end of file diff --git a/Lib/Alisms/Psr/Http/Message/MessageInterface.php b/Lib/Alisms/Psr/Http/Message/MessageInterface.php new file mode 100644 index 0000000..8f67a05 --- /dev/null +++ b/Lib/Alisms/Psr/Http/Message/MessageInterface.php @@ -0,0 +1,187 @@ +getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->getHeaders() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return array Returns an associative array of the message's headers. Each + * key MUST be a header name, and each value MUST be an array of strings + * for that header. + */ + public function getHeaders(); + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader($name); + + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function getHeader($name); + + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function getHeaderLine($name); + + /** + * Return an instance with the provided value replacing the specified header. + * + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + * @return self + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withHeader($name, $value); + + /** + * Return an instance with the specified header appended with the given value. + * + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new header and/or value. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + * @return self + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withAddedHeader($name, $value); + + /** + * Return an instance without the specified header. + * + * Header resolution MUST be done without case-sensitivity. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the named header. + * + * @param string $name Case-insensitive header field name to remove. + * @return self + */ + public function withoutHeader($name); + + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ + public function getBody(); + + /** + * Return an instance with the specified message body. + * + * The body MUST be a StreamInterface object. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that has the + * new body stream. + * + * @param StreamInterface $body Body. + * @return self + * @throws \InvalidArgumentException When the body is not valid. + */ + public function withBody(StreamInterface $body); +} diff --git a/Lib/Alisms/Psr/Http/Message/RequestInterface.php b/Lib/Alisms/Psr/Http/Message/RequestInterface.php new file mode 100644 index 0000000..75c802e --- /dev/null +++ b/Lib/Alisms/Psr/Http/Message/RequestInterface.php @@ -0,0 +1,129 @@ +getQuery()` + * or from the `QUERY_STRING` server param. + * + * @return array + */ + public function getQueryParams(); + + /** + * Return an instance with the specified query string arguments. + * + * These values SHOULD remain immutable over the course of the incoming + * request. They MAY be injected during instantiation, such as from PHP's + * $_GET superglobal, or MAY be derived from some other value such as the + * URI. In cases where the arguments are parsed from the URI, the data + * MUST be compatible with what PHP's parse_str() would return for + * purposes of how duplicate query parameters are handled, and how nested + * sets are handled. + * + * Setting query string arguments MUST NOT change the URI stored by the + * request, nor the values in the server params. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated query string arguments. + * + * @param array $query Array of query string arguments, typically from + * $_GET. + * @return self + */ + public function withQueryParams(array $query); + + /** + * Retrieve normalized file upload data. + * + * This method returns upload metadata in a normalized tree, with each leaf + * an instance of Psr\Http\Message\UploadedFileInterface. + * + * These values MAY be prepared from $_FILES or the message body during + * instantiation, or MAY be injected via withUploadedFiles(). + * + * @return array An array tree of UploadedFileInterface instances; an empty + * array MUST be returned if no data is present. + */ + public function getUploadedFiles(); + + /** + * Create a new instance with the specified uploaded files. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param array An array tree of UploadedFileInterface instances. + * @return self + * @throws \InvalidArgumentException if an invalid structure is provided. + */ + public function withUploadedFiles(array $uploadedFiles); + + /** + * Retrieve any parameters provided in the request body. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, this method MUST + * return the contents of $_POST. + * + * Otherwise, this method may return any results of deserializing + * the request body content; as parsing returns structured content, the + * potential types MUST be arrays or objects only. A null value indicates + * the absence of body content. + * + * @return null|array|object The deserialized body parameters, if any. + * These will typically be an array or object. + */ + public function getParsedBody(); + + /** + * Return an instance with the specified body parameters. + * + * These MAY be injected during instantiation. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, use this method + * ONLY to inject the contents of $_POST. + * + * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of + * deserializing the request body content. Deserialization/parsing returns + * structured data, and, as such, this method ONLY accepts arrays or objects, + * or a null value if nothing was available to parse. + * + * As an example, if content negotiation determines that the request data + * is a JSON payload, this method could be used to create a request + * instance with the deserialized parameters. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param null|array|object $data The deserialized body data. This will + * typically be in an array or object. + * @return self + * @throws \InvalidArgumentException if an unsupported argument type is + * provided. + */ + public function withParsedBody($data); + + /** + * Retrieve attributes derived from the request. + * + * The request "attributes" may be used to allow injection of any + * parameters derived from the request: e.g., the results of path + * match operations; the results of decrypting cookies; the results of + * deserializing non-form-encoded message bodies; etc. Attributes + * will be application and request specific, and CAN be mutable. + * + * @return array Attributes derived from the request. + */ + public function getAttributes(); + + /** + * Retrieve a single derived request attribute. + * + * Retrieves a single derived request attribute as described in + * getAttributes(). If the attribute has not been previously set, returns + * the default value as provided. + * + * This method obviates the need for a hasAttribute() method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * @return mixed + */ + public function getAttribute($name, $default = null); + + /** + * Return an instance with the specified derived request attribute. + * + * This method allows setting a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $value The value of the attribute. + * @return self + */ + public function withAttribute($name, $value); + + /** + * Return an instance that removes the specified derived request attribute. + * + * This method allows removing a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @return self + */ + public function withoutAttribute($name); +} diff --git a/Lib/Alisms/Psr/Http/Message/StreamInterface.php b/Lib/Alisms/Psr/Http/Message/StreamInterface.php new file mode 100644 index 0000000..f68f391 --- /dev/null +++ b/Lib/Alisms/Psr/Http/Message/StreamInterface.php @@ -0,0 +1,158 @@ + + * [user-info@]host[:port] + * + * + * If the port component is not set or is the standard port for the current + * scheme, it SHOULD NOT be included. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.2 + * @return string The URI authority, in "[user-info@]host[:port]" format. + */ + public function getAuthority(); + + /** + * Retrieve the user information component of the URI. + * + * If no user information is present, this method MUST return an empty + * string. + * + * If a user is present in the URI, this will return that value; + * additionally, if the password is also present, it will be appended to the + * user value, with a colon (":") separating the values. + * + * The trailing "@" character is not part of the user information and MUST + * NOT be added. + * + * @return string The URI user information, in "username[:password]" format. + */ + public function getUserInfo(); + + /** + * Retrieve the host component of the URI. + * + * If no host is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.2.2. + * + * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 + * @return string The URI host. + */ + public function getHost(); + + /** + * Retrieve the port component of the URI. + * + * If a port is present, and it is non-standard for the current scheme, + * this method MUST return it as an integer. If the port is the standard port + * used with the current scheme, this method SHOULD return null. + * + * If no port is present, and no scheme is present, this method MUST return + * a null value. + * + * If no port is present, but a scheme is present, this method MAY return + * the standard port for that scheme, but SHOULD return null. + * + * @return null|int The URI port. + */ + public function getPort(); + + /** + * Retrieve the path component of the URI. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * Normally, the empty path "" and absolute path "/" are considered equal as + * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically + * do this normalization because in contexts with a trimmed base path, e.g. + * the front controller, this difference becomes significant. It's the task + * of the user to handle both "" and "/". + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.3. + * + * As an example, if the value should include a slash ("/") not intended as + * delimiter between path segments, that value MUST be passed in encoded + * form (e.g., "%2F") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.3 + * @return string The URI path. + */ + public function getPath(); + + /** + * Retrieve the query string of the URI. + * + * If no query string is present, this method MUST return an empty string. + * + * The leading "?" character is not part of the query and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.4. + * + * As an example, if a value in a key/value pair of the query string should + * include an ampersand ("&") not intended as a delimiter between values, + * that value MUST be passed in encoded form (e.g., "%26") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.4 + * @return string The URI query string. + */ + public function getQuery(); + + /** + * Retrieve the fragment component of the URI. + * + * If no fragment is present, this method MUST return an empty string. + * + * The leading "#" character is not part of the fragment and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.5. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.5 + * @return string The URI fragment. + */ + public function getFragment(); + + /** + * Return an instance with the specified scheme. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified scheme. + * + * Implementations MUST support the schemes "http" and "https" case + * insensitively, and MAY accommodate other schemes if required. + * + * An empty scheme is equivalent to removing the scheme. + * + * @param string $scheme The scheme to use with the new instance. + * @return self A new instance with the specified scheme. + * @throws \InvalidArgumentException for invalid or unsupported schemes. + */ + public function withScheme($scheme); + + /** + * Return an instance with the specified user information. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified user information. + * + * Password is optional, but the user information MUST include the + * user; an empty string for the user is equivalent to removing user + * information. + * + * @param string $user The user name to use for authority. + * @param null|string $password The password associated with $user. + * @return self A new instance with the specified user information. + */ + public function withUserInfo($user, $password = null); + + /** + * Return an instance with the specified host. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified host. + * + * An empty host value is equivalent to removing the host. + * + * @param string $host The hostname to use with the new instance. + * @return self A new instance with the specified host. + * @throws \InvalidArgumentException for invalid hostnames. + */ + public function withHost($host); + + /** + * Return an instance with the specified port. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified port. + * + * Implementations MUST raise an exception for ports outside the + * established TCP and UDP port ranges. + * + * A null value provided for the port is equivalent to removing the port + * information. + * + * @param null|int $port The port to use with the new instance; a null value + * removes the port information. + * @return self A new instance with the specified port. + * @throws \InvalidArgumentException for invalid ports. + */ + public function withPort($port); + + /** + * Return an instance with the specified path. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified path. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * If the path is intended to be domain-relative rather than path relative then + * it must begin with a slash ("/"). Paths not starting with a slash ("/") + * are assumed to be relative to some base path known to the application or + * consumer. + * + * Users can provide both encoded and decoded path characters. + * Implementations ensure the correct encoding as outlined in getPath(). + * + * @param string $path The path to use with the new instance. + * @return self A new instance with the specified path. + * @throws \InvalidArgumentException for invalid paths. + */ + public function withPath($path); + + /** + * Return an instance with the specified query string. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified query string. + * + * Users can provide both encoded and decoded query characters. + * Implementations ensure the correct encoding as outlined in getQuery(). + * + * An empty query string value is equivalent to removing the query string. + * + * @param string $query The query string to use with the new instance. + * @return self A new instance with the specified query string. + * @throws \InvalidArgumentException for invalid query strings. + */ + public function withQuery($query); + + /** + * Return an instance with the specified URI fragment. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified URI fragment. + * + * Users can provide both encoded and decoded fragment characters. + * Implementations ensure the correct encoding as outlined in getFragment(). + * + * An empty fragment value is equivalent to removing the fragment. + * + * @param string $fragment The fragment to use with the new instance. + * @return self A new instance with the specified fragment. + */ + public function withFragment($fragment); + + /** + * Return the string representation as a URI reference. + * + * Depending on which components of the URI are present, the resulting + * string is either a full URI or relative reference according to RFC 3986, + * Section 4.1. The method concatenates the various components of the URI, + * using the appropriate delimiters: + * + * - If a scheme is present, it MUST be suffixed by ":". + * - If an authority is present, it MUST be prefixed by "//". + * - The path can be concatenated without delimiters. But there are two + * cases where the path has to be adjusted to make the URI reference + * valid as PHP does not allow to throw an exception in __toString(): + * - If the path is rootless and an authority is present, the path MUST + * be prefixed by "/". + * - If the path is starting with more than one "/" and no authority is + * present, the starting slashes MUST be reduced to one. + * - If a query is present, it MUST be prefixed by "?". + * - If a fragment is present, it MUST be prefixed by "#". + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + * @return string + */ + public function __toString(); +} diff --git a/Lib/Alisms/README.md b/Lib/Alisms/README.md new file mode 100644 index 0000000..3d97187 --- /dev/null +++ b/Lib/Alisms/README.md @@ -0,0 +1,5 @@ +# MNS SDK for PHP +Please refer to http://www.aliyun.com/product/mns and https://docs.aliyun.com/?spm=5176.7393424.9.6.5ki1hv#/pub/mns/api_reference/intro&intro for more API details. + +## Samples +You must fulfill the AccessId/AccessKey/AccountID in the example before running. diff --git a/Lib/Alisms/Samples/Queue/CreateQueueAndSendMessage.php b/Lib/Alisms/Samples/Queue/CreateQueueAndSendMessage.php new file mode 100644 index 0000000..432a08a --- /dev/null +++ b/Lib/Alisms/Samples/Queue/CreateQueueAndSendMessage.php @@ -0,0 +1,116 @@ +accessId = $accessId; + $this->accessKey = $accessKey; + $this->endPoint = $endPoint; + } + + public function run() + { + $queueName = "CreateQueueAndSendMessageExample"; + + $this->client = new Client($this->endPoint, $this->accessId, $this->accessKey); + + // 1. create queue + $request = new CreateQueueRequest($queueName); + try + { + $res = $this->client->createQueue($request); + echo "QueueCreated! \n"; + } + catch (MnsException $e) + { + echo "CreateQueueFailed: " . $e; + return; + } + $queue = $this->client->getQueueRef($queueName); + + // 2. send message + $messageBody = "test"; + // as the messageBody will be automatically encoded + // the MD5 is calculated for the encoded body + $bodyMD5 = md5(base64_encode($messageBody)); + $request = new SendMessageRequest($messageBody); + try + { + $res = $queue->sendMessage($request); + echo "MessageSent! \n"; + } + catch (MnsException $e) + { + echo "SendMessage Failed: " . $e; + return; + } + + // 3. receive message + $receiptHandle = NULL; + try + { + // when receiving messages, it's always a good practice to set the waitSeconds to be 30. + // it means to send one http-long-polling request which lasts 30 seconds at most. + $res = $queue->receiveMessage(30); + echo "ReceiveMessage Succeed! \n"; + if (strtoupper($bodyMD5) == $res->getMessageBodyMD5()) + { + echo "You got the message sent by yourself! \n"; + } + $receiptHandle = $res->getReceiptHandle(); + } + catch (MnsException $e) + { + echo "ReceiveMessage Failed: " . $e; + return; + } + + // 4. delete message + try + { + $res = $queue->deleteMessage($receiptHandle); + echo "DeleteMessage Succeed! \n"; + } + catch (MnsException $e) + { + echo "DeleteMessage Failed: " . $e; + return; + } + + // 5. delete queue + try { + $this->client->deleteQueue($queueName); + echo "DeleteQueue Succeed! \n"; + } catch (MnsException $e) { + echo "DeleteQueue Failed: " . $e; + return; + } + } +} + +$accessId = ""; +$accessKey = ""; +$endPoint = ""; + +if (empty($accessId) || empty($accessKey) || empty($endPoint)) +{ + echo "Must Provide AccessId/AccessKey/EndPoint to Run the Example. \n"; + return; +} + +$instance = new CreateQueueAndSendMessage($accessId, $accessKey, $endPoint); +$instance->run(); + +?> diff --git a/Lib/Alisms/Samples/Topic/CreateTopicAndPublishMessage.php b/Lib/Alisms/Samples/Topic/CreateTopicAndPublishMessage.php new file mode 100644 index 0000000..f5a0246 --- /dev/null +++ b/Lib/Alisms/Samples/Topic/CreateTopicAndPublishMessage.php @@ -0,0 +1,125 @@ +ip = $ip; + $this->port = strval($port); + $this->accessId = $accessId; + $this->accessKey = $accessKey; + $this->endPoint = $endPoint; + } + + public function run() + { + $topicName = "CreateTopicAndPublishMessageExample"; + + $this->client = new Client($this->endPoint, $this->accessId, $this->accessKey); + + // 1. create topic + $request = new CreateTopicRequest($topicName); + try + { + $res = $this->client->createTopic($request); + echo "TopicCreated! \n"; + } + catch (MnsException $e) + { + echo "CreateTopicFailed: " . $e; + return; + } + $topic = $this->client->getTopicRef($topicName); + + // 2. subscribe + $subscriptionName = "SubscriptionExample"; + $attributes = new SubscriptionAttributes($subscriptionName, 'http://' . $this->ip . ':' . $this->port); + + try + { + $topic->subscribe($attributes); + echo "Subscribed! \n"; + } + catch (MnsException $e) + { + echo "SubscribeFailed: " . $e; + return; + } + + // 3. send message + $messageBody = "test"; + // as the messageBody will be automatically encoded + // the MD5 is calculated for the encoded body + $bodyMD5 = md5(base64_encode($messageBody)); + $request = new PublishMessageRequest($messageBody); + try + { + $res = $topic->publishMessage($request); + echo "MessagePublished! \n"; + } + catch (MnsException $e) + { + echo "PublishMessage Failed: " . $e; + return; + } + + // 4. sleep for receiving notification + sleep(20); + + // 5. unsubscribe + try + { + $topic->unsubscribe($subscriptionName); + echo "Unsubscribe Succeed! \n"; + } + catch (MnsException $e) + { + echo "Unsubscribe Failed: " . $e; + return; + } + + // 6. delete topic + try + { + $this->client->deleteTopic($topicName); + echo "DeleteTopic Succeed! \n"; + } + catch (MnsException $e) + { + echo "DeleteTopic Failed: " . $e; + return; + } + } +} + +$accessId = ""; +$accessKey = ""; +$endPoint = ""; +$ip = ""; //公网IP +$port = "8000"; + +if (empty($accessId) || empty($accessKey) || empty($endPoint)) +{ + echo "Must Provide AccessId/AccessKey/EndPoint to Run the Example. \n"; + return; +} + + +$instance = new CreateTopicAndPublishMessage($ip, $port, $accessId, $accessKey, $endPoint); +$instance->run(); + +?> diff --git a/Lib/Alisms/Samples/Topic/http_server_sample.php b/Lib/Alisms/Samples/Topic/http_server_sample.php new file mode 100644 index 0000000..a5cca27 --- /dev/null +++ b/Lib/Alisms/Samples/Topic/http_server_sample.php @@ -0,0 +1,113 @@ + $value) + { + if (substr($name, 0, 5) == 'HTTP_') + { + $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; + } + } + return $headers; + } +} + +// 1. get the headers and check the signature +$tmpHeaders = array(); +$headers = getallheaders(); +foreach ($headers as $key => $value) +{ + if (0 === strpos($key, 'x-mns-')) + { + $tmpHeaders[$key] = $value; + } +} +ksort($tmpHeaders); +$canonicalizedMNSHeaders = implode("\n", array_map(function ($v, $k) { return $k . ":" . $v; }, $tmpHeaders, array_keys($tmpHeaders))); + +$method = $_SERVER['REQUEST_METHOD']; +$canonicalizedResource = $_SERVER['REQUEST_URI']; +error_log($canonicalizedResource); + +$contentMd5 = ''; +if (array_key_exists('Content-MD5', $headers)) +{ + $contentMd5 = $headers['Content-MD5']; +} +else if (array_key_exists('Content-md5', $headers)) +{ + $contentMd5 = $headers['Content-md5']; +} + +$contentType = ''; +if (array_key_exists('Content-Type', $headers)) +{ + $contentType = $headers['Content-Type']; +} +$date = $headers['Date']; + +$stringToSign = strtoupper($method) . "\n" . $contentMd5 . "\n" . $contentType . "\n" . $date . "\n" . $canonicalizedMNSHeaders . "\n" . $canonicalizedResource; +error_log($stringToSign); + +$publicKeyURL = base64_decode($headers['x-mns-signing-cert-url']); +$publicKey = get_by_url($publicKeyURL); +$signature = $headers['Authorization']; + +$pass = verify($stringToSign, $signature, $publicKey); +if (!$pass) +{ + error_log("verify signature fail"); + http_response_code(400); + return; +} + +// 2. now parse the content +$content = file_get_contents("php://input"); +error_log($content); + +if (!empty($contentMd5) && $contentMd5 != base64_encode(md5($content))) +{ + error_log("md5 mismatch"); + http_response_code(401); + return; +} + +$msg = new SimpleXMLElement($content); +echo "\n______________________________________________________\n"; +echo "TopicName: " . $msg->TopicName . "\n"; +echo "SubscriptionName: " . $msg->SubscriptionName . "\n"; +echo "MessageId: " . $msg->MessageId . "\n"; +echo "MessageMD5: " . $msg->MessageMD5 . "\n"; +echo "Message: " . $msg->Message . "\n"; +echo "______________________________________________________\n"; +http_response_code(200); + + +?> diff --git a/Lib/Alisms/Tests/ClientTest.php b/Lib/Alisms/Tests/ClientTest.php new file mode 100644 index 0000000..09b6dfe --- /dev/null +++ b/Lib/Alisms/Tests/ClientTest.php @@ -0,0 +1,539 @@ +endPoint = $ini_array["endpoint"]; + $this->accessId = $ini_array["accessid"]; + $this->accessKey = $ini_array["accesskey"]; + + $this->queueToDelete = array(); + $this->topicToDelete = array(); + + $this->client = new Client($this->endPoint, $this->accessId, $this->accessKey); + } + + public function tearDown() + { + foreach ($this->queueToDelete as $queueName) + { + try { + $this->client->deleteQueue($queueName); + } catch (\Exception $e) { + } + } + foreach ($this->topicToDelete as $topicName) + { + try { + $this->client->deleteTopic($topicName); + } catch (\Exception $e) { + } + } + } + + public function testAccountAttributes() + { + try + { + $attributes = new AccountAttributes; + $attributes->setLoggingBucket("Test"); + $this->client->setAccountAttributes($attributes); + $res = $this->client->getAccountAttributes(); + $this->assertTrue($res->isSucceed()); + $this->assertEquals("Test", $res->getAccountAttributes()->getLoggingBucket()); + + $attributes = new AccountAttributes; + $this->client->setAccountAttributes($attributes); + $res = $this->client->getAccountAttributes(); + $this->assertTrue($res->isSucceed()); + $this->assertEquals("Test", $res->getAccountAttributes()->getLoggingBucket()); + + $attributes = new AccountAttributes; + $attributes->setLoggingBucket(""); + $this->client->setAccountAttributes($attributes); + $res = $this->client->getAccountAttributes(); + $this->assertTrue($res->isSucceed()); + $this->assertEquals("", $res->getAccountAttributes()->getLoggingBucket()); + } + catch (MnsException $e) + { + $this->assertEquals($e->getMnsErrorCode(), Constants::INVALID_ARGUMENT); + } + } + + public function testCreateQueueAsync() + { + $queueName = "testCreateQueueAsync"; + $request = new CreateQueueRequest($queueName); + $this->queueToDelete[] = $queueName; + + // Async Call with callback + try + { + $res = $this->client->createQueueAsync($request, + new AsyncCallback( + function($response) { + $this->assertTrue($response->isSucceed()); + }, + function($e) { + $this->assertTrue(FALSE, $e); + } + ) + ); + $res = $res->wait(); + + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + // Async call without callback + try + { + $res = $this->client->createQueueAsync($request); + $res = $res->wait(); + + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + } + + public function testCreateQueueSync() + { + $queueName = "testCreateQueueSync"; + + // 1. create queue with InvalidArgument + $attributes = new QueueAttributes; + $attributes->setPollingWaitSeconds(60); + + $request = new CreateQueueRequest($queueName, $attributes); + try + { + $res = $this->client->createQueue($request); + $this->assertTrue(FALSE, "Should throw InvalidArgumentException"); + } + catch (MnsException $e) + { + $this->assertEquals($e->getMnsErrorCode(), Constants::INVALID_ARGUMENT); + } + + // 2. create queue + $request = new CreateQueueRequest($queueName); + $this->queueToDelete[] = $queueName; + try + { + $res = $this->client->createQueue($request); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + // 3. create queue with same attributes + $request = new CreateQueueRequest($queueName); + $this->queueToDelete[] = $queueName; + try + { + $res = $this->client->createQueue($request); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + // 4. create same queue with different attributes + $attributes = new QueueAttributes; + $attributes->setPollingWaitSeconds(20); + + $request = new CreateQueueRequest($queueName, $attributes); + try + { + $res = $this->client->createQueue($request); + $this->assertTrue(FALSE, "Should throw QueueAlreadyExistException"); + } + catch (MnsException $e) + { + $this->assertEquals($e->getMnsErrorCode(), Constants::QUEUE_ALREADY_EXIST); + } + } + + public function testListQueue() + { + $queueNamePrefix = uniqid(); + $queueName1 = $queueNamePrefix . "testListQueue1"; + $queueName2 = $queueNamePrefix . "testListQueue2"; + + // 1. create queue + $request = new CreateQueueRequest($queueName1); + $this->queueToDelete[] = $queueName1; + try + { + $res = $this->client->createQueue($request); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $request = new CreateQueueRequest($queueName2); + $this->queueToDelete[] = $queueName2; + try + { + $res = $this->client->createQueue($request); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + // 2. list queue + $queueName1Found = FALSE; + $queueName2Found = FALSE; + + $count = 0; + $request = new ListQueueRequest(1, $queueNamePrefix); + + while ($count < 2) { + try + { + $res = $this->client->listQueue($request); + $this->assertTrue($res->isSucceed()); + + $queueNames = $res->getQueueNames(); + foreach ($queueNames as $queueName) { + if ($queueName == $queueName1) { + $queueName1Found = TRUE; + } elseif ($queueName == $queueName2) { + $queueName2Found = TRUE; + } else { + $this->assertTrue(FALSE, $queueName . " Should not be here."); + } + } + + if ($count > 0) { + $this->assertTrue($res->isFinished(), implode(", ", $queueNames)); + } + $request->setMarker($res->getNextMarker()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + $count += 1; + } + + $this->assertTrue($queueName1Found, $queueName1 . " Not Found!"); + $this->assertTrue($queueName2Found, $queueName2 . " Not Found!"); + } + + public function testListQueueAsync() + { + $queueNamePrefix = uniqid(); + $queueName1 = $queueNamePrefix . "testListQueue1"; + $queueName2 = $queueNamePrefix . "testListQueue2"; + + // 1. create queue + $request = new CreateQueueRequest($queueName1); + $this->queueToDelete[] = $queueName1; + try + { + $res = $this->client->createQueue($request); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $request = new CreateQueueRequest($queueName2); + $this->queueToDelete[] = $queueName2; + try + { + $res = $this->client->createQueue($request); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + // 2. list queue + $queueName1Found = FALSE; + $queueName2Found = FALSE; + + $count = 0; + $request = new ListQueueRequest(1, $queueNamePrefix); + + while ($count < 2) { + try + { + $res = $this->client->listQueueAsync($request, + new AsyncCallback( + function($response) use ($count, &$request, $queueName1, $queueName2, &$queueName1Found, &$queueName2Found) { + $this->assertTrue($response->isSucceed()); + + $queueNames = $response->getQueueNames(); + foreach ($queueNames as $queueName) { + if ($queueName == $queueName1) { + $queueName1Found = TRUE; + } elseif ($queueName == $queueName2) { + $queueName2Found = TRUE; + } else { + $this->assertTrue(FALSE, $queueName . " Should not be here."); + } + } + + if ($count > 0) { + $this->assertTrue($response->isFinished(), implode(", ", $queueNames)); + } + $request->setMarker($response->getNextMarker()); + }, + function($e) { + $this->assertTrue(FALSE, $e); + } + ) + ); + $res = $res->wait(); + + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + $count += 1; + } + + $this->assertTrue($queueName1Found, $queueName1 . " Not Found!"); + $this->assertTrue($queueName2Found, $queueName2 . " Not Found!"); + } + + public function testDeleteQueue() + { + $queueName = "testDeleteQueue"; + + // 1. create queue + $request = new CreateQueueRequest($queueName); + $this->queueToDelete[] = $queueName; + try + { + $res = $this->client->createQueue($request); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + // 2. delete queue + try + { + $res = $this->client->deleteQueue($queueName); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + } + + public function testDeleteQueueAsync() + { + $queueName = "testDeleteQueueAsync"; + + // 1. create queue + $request = new CreateQueueRequest($queueName); + $this->queueToDelete[] = $queueName; + try + { + $res = $this->client->createQueue($request); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + // 2. delete Queue + try + { + $res = $this->client->deleteQueueAsync($queueName); + $res = $res->wait(); + + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + } + + public function testCreateTopicSync() + { + $topicName = "testCreateTopicSync"; + + // 1. create topic with InvalidArgument + $attributes = new TopicAttributes; + $attributes->setMaximumMessageSize(65 * 1024); + + $request = new CreateTopicRequest($topicName, $attributes); + try + { + $res = $this->client->createTopic($request); + $this->assertTrue(FALSE, "Should throw InvalidArgumentException"); + } + catch (MnsException $e) + { + $this->assertEquals($e->getMnsErrorCode(), Constants::INVALID_ARGUMENT); + } + + // 2. create topic + $request = new CreateTopicRequest($topicName); + $this->topicToDelete[] = $topicName; + try + { + $res = $this->client->createTopic($request); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + // 3. create topic with same attributes + $request = new CreateTopicRequest($topicName); + $this->topicToDelete[] = $topicName; + try + { + $res = $this->client->createTopic($request); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + // 4. create same topic with different attributes + $attributes = new TopicAttributes; + $attributes->setMaximumMessageSize(10 * 1024); + + $request = new CreateTopicRequest($topicName, $attributes); + try + { + $res = $this->client->createTopic($request); + $this->assertTrue(FALSE, "Should throw TopicAlreadyExistException"); + } + catch (MnsException $e) + { + $this->assertEquals($e->getMnsErrorCode(), Constants::TOPIC_ALREADY_EXIST); + } + } + + public function testListTopic() + { + $topicNamePrefix = uniqid(); + $topicName1 = $topicNamePrefix . "testListTopic1"; + $topicName2 = $topicNamePrefix . "testListTopic2"; + + // 1. create Topic + $request = new CreateTopicRequest($topicName1); + $this->topicToDelete[] = $topicName1; + try + { + $res = $this->client->createTopic($request); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $request = new CreateTopicRequest($topicName2); + $this->topicToDelete[] = $topicName2; + try + { + $res = $this->client->createTopic($request); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + // 2. list Topic + $topicName1Found = FALSE; + $topicName2Found = FALSE; + + $count = 0; + $request = new ListTopicRequest(1, $topicNamePrefix); + + while ($count < 2) { + try + { + $res = $this->client->listTopic($request); + $this->assertTrue($res->isSucceed()); + + $topicNames = $res->getTopicNames(); + foreach ($topicNames as $topicName) { + if ($topicName == $topicName1) { + $topicName1Found = TRUE; + } elseif ($topicName == $topicName2) { + $topicName2Found = TRUE; + } else { + $this->assertTrue(FALSE, $topicName . " Should not be here."); + } + } + + if ($count > 0) { + $this->assertTrue($res->isFinished(), implode(", ", $topicNames)); + } + $request->setMarker($res->getNextMarker()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + $count += 1; + } + + $this->assertTrue($topicName1Found, $topicName1 . " Not Found!"); + $this->assertTrue($topicName2Found, $topicName2 . " Not Found!"); + } +} + +?> diff --git a/Lib/Alisms/Tests/QueueTest.php b/Lib/Alisms/Tests/QueueTest.php new file mode 100644 index 0000000..f2a0050 --- /dev/null +++ b/Lib/Alisms/Tests/QueueTest.php @@ -0,0 +1,494 @@ +endPoint = $ini_array["endpoint"]; + $this->accessId = $ini_array["accessid"]; + $this->accessKey = $ini_array["accesskey"]; + + $this->queueToDelete = array(); + + $this->client = new Client($this->endPoint, $this->accessId, $this->accessKey); + } + + public function tearDown() + { + foreach ($this->queueToDelete as $queueName) + { + try { + $this->client->deleteQueue($queueName); + } catch (\Exception $e) { + } + } + } + + private function prepareQueue($queueName, $attributes = NULL, $base64=TRUE) + { + $request = new CreateQueueRequest($queueName, $attributes); + $this->queueToDelete[] = $queueName; + try + { + $res = $this->client->createQueue($request); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + return $this->client->getQueueRef($queueName, $base64); + } + + public function testLoggingEnabled() + { + $queueName = "testLoggingEnabled"; + $queue = $this->prepareQueue($queueName); + + try + { + $attributes = new QueueAttributes; + $attributes->setLoggingEnabled(false); + $queue->setAttribute($attributes); + $res = $queue->getAttribute(); + $this->assertTrue($res->isSucceed()); + $this->assertEquals(false, $res->getQueueAttributes()->getLoggingEnabled()); + + $attributes = new QueueAttributes; + $attributes->setLoggingEnabled(true); + $queue->setAttribute($attributes); + $res = $queue->getAttribute(); + $this->assertTrue($res->isSucceed()); + $this->assertEquals(true, $res->getQueueAttributes()->getLoggingEnabled()); + + $attributes = new QueueAttributes; + $queue->setAttribute($attributes); + $res = $queue->getAttribute(); + $this->assertTrue($res->isSucceed()); + $this->assertEquals(true, $res->getQueueAttributes()->getLoggingEnabled()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + } + + public function testQueueAttributes() + { + $queueName = "testQueueAttributes"; + $queue = $this->prepareQueue($queueName); + + try + { + $res = $queue->getAttribute(); + $this->assertTrue($res->isSucceed()); + $this->assertEquals($queueName, $res->getQueueAttributes()->getQueueName()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $delaySeconds = 3; + $attributes = new QueueAttributes; + $attributes->setDelaySeconds($delaySeconds); + try + { + $res = $queue->setAttribute($attributes); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + try + { + $res = $queue->getAttribute(); + $this->assertTrue($res->isSucceed()); + $this->assertEquals($res->getQueueAttributes()->getDelaySeconds(), $delaySeconds); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + } + + public function testMessageDelaySeconds() + { + $queueName = "testMessageDelaySeconds" . uniqid(); + $queue = $this->prepareQueue($queueName, NULL, FALSE); + + $messageBody = "test"; + $bodyMD5 = md5($messageBody); + $delaySeconds = 1; + $request = new SendMessageRequest($messageBody, $delaySeconds); + $receiptHandle = NULL; + try + { + $res = $queue->sendMessage($request); + $this->assertTrue($res->isSucceed()); + $this->assertEquals(strtoupper($bodyMD5), $res->getMessageBodyMD5()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + } + + public function testMessageNoBase64() + { + $queueName = "testQueueAttributes" . uniqid(); + $queue = $this->prepareQueue($queueName, NULL, FALSE); + + $messageBody = "test"; + $bodyMD5 = md5($messageBody); + $request = new SendMessageRequest($messageBody); + try + { + $res = $queue->sendMessage($request); + $this->assertTrue($res->isSucceed()); + $this->assertEquals(strtoupper($bodyMD5), $res->getMessageBodyMD5()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + try + { + $res = $queue->peekMessage(); + $this->assertTrue($res->isSucceed()); + $this->assertEquals(strtoupper($bodyMD5), $res->getMessageBodyMD5()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $receiptHandle = NULL; + try + { + $res = $queue->receiveMessage(); + $this->assertTrue($res->isSucceed()); + $this->assertEquals(strtoupper($bodyMD5), $res->getMessageBodyMD5()); + + $receiptHandle = $res->getReceiptHandle(); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $newReceiptHandle = NULL; + try + { + $res = $queue->changeMessageVisibility($receiptHandle, 18); + $this->assertTrue($res->isSucceed()); + $newReceiptHandle = $res->getReceiptHandle(); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + try + { + $res = $queue->deleteMessage($receiptHandle); + $this->assertTrue(FALSE, "Should NOT reach here!"); + } + catch (MnsException $e) + { + $this->assertEquals(Constants::MESSAGE_NOT_EXIST, $e->getMnsErrorCode()); + } + + try + { + $res = $queue->deleteMessage($newReceiptHandle); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + } + + public function testMessage() + { + $queueName = "testQueueAttributes" . uniqid(); + $queue = $this->prepareQueue($queueName); + + $messageBody = "test"; + $bodyMD5 = md5(base64_encode($messageBody)); + $request = new SendMessageRequest($messageBody); + try + { + $res = $queue->sendMessage($request); + $this->assertTrue($res->isSucceed()); + $this->assertEquals(strtoupper($bodyMD5), $res->getMessageBodyMD5()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + try + { + $res = $queue->peekMessage(); + $this->assertTrue($res->isSucceed()); + $this->assertEquals(strtoupper($bodyMD5), $res->getMessageBodyMD5()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $receiptHandle = NULL; + try + { + $res = $queue->receiveMessage(); + $this->assertTrue($res->isSucceed()); + $this->assertEquals(strtoupper($bodyMD5), $res->getMessageBodyMD5()); + + $receiptHandle = $res->getReceiptHandle(); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $newReceiptHandle = NULL; + try + { + $res = $queue->changeMessageVisibility($receiptHandle, 18); + $this->assertTrue($res->isSucceed()); + $newReceiptHandle = $res->getReceiptHandle(); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + try + { + $res = $queue->deleteMessage($receiptHandle); + $this->assertTrue(FALSE, "Should NOT reach here!"); + } + catch (MnsException $e) + { + $this->assertEquals(Constants::MESSAGE_NOT_EXIST, $e->getMnsErrorCode()); + } + + try + { + $res = $queue->deleteMessage($newReceiptHandle); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + } + + public function testBatchNoBase64() + { + $queueName = "testBatch" . uniqid(); + $queue = $this->prepareQueue($queueName, NULL, FALSE); + + $messageBody = "test"; + $bodyMD5 = md5($messageBody); + + $numOfMessages = 3; + + $item = new SendMessageRequestItem($messageBody); + $items = array($item, $item, $item); + $request = new BatchSendMessageRequest($items); + try + { + $res = $queue->batchSendMessage($request); + $this->assertTrue($res->isSucceed()); + + $responseItems = $res->getSendMessageResponseItems(); + $this->assertTrue(count($responseItems) == 3); + foreach ($responseItems as $item) + { + $this->assertEquals(strtoupper($bodyMD5), $item->getMessageBodyMD5()); + } + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + if ($e instanceof BatchSendFailException) + { + var_dump($e->getSendMessageResponseItems()); + } + } + + try + { + $res = $queue->batchPeekMessage($numOfMessages); + $this->assertTrue($res->isSucceed()); + + $messages = $res->getMessages(); + $this->assertEquals($numOfMessages, count($messages)); + foreach ($messages as $message) + { + $this->assertEquals(strtoupper($bodyMD5), $message->getMessageBodyMD5()); + } + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $receiptHandles = array(); + $request = new BatchReceiveMessageRequest($numOfMessages); + try + { + $res = $queue->batchReceiveMessage($request); + $this->assertTrue($res->isSucceed()); + + $messages = $res->getMessages(); + $this->assertEquals($numOfMessages, count($messages)); + foreach ($messages as $message) + { + $this->assertEquals(strtoupper($bodyMD5), $message->getMessageBodyMD5()); + $receiptHandles[] = $message->getReceiptHandle(); + } + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $errorReceiptHandle = "1-ODU4OTkzNDU5My0xNDM1MTk3NjAwLTItNg=="; + $receiptHandles[] = $errorReceiptHandle; + try + { + $res = $queue->batchDeleteMessage($receiptHandles); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue($e instanceof BatchDeleteFailException); + $items = $e->getDeleteMessageErrorItems(); + $this->assertEquals(1, count($items)); + $this->assertEquals($errorReceiptHandle, $items[0]->getReceiptHandle()); + } + } + + public function testBatch() + { + $queueName = "testBatch" . uniqid(); + $queue = $this->prepareQueue($queueName); + + $messageBody = "test"; + $bodyMD5 = md5(base64_encode($messageBody)); + + $numOfMessages = 3; + + $item = new SendMessageRequestItem($messageBody); + $items = array($item, $item, $item); + $request = new BatchSendMessageRequest($items); + try + { + $res = $queue->batchSendMessage($request); + $this->assertTrue($res->isSucceed()); + + $responseItems = $res->getSendMessageResponseItems(); + foreach ($responseItems as $item) + { + $this->assertEquals(strtoupper($bodyMD5), $item->getMessageBodyMD5()); + } + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + if ($e instanceof BatchSendFailException) + { + var_dump($e->getSendMessageResponseItems()); + } + } + + try + { + $res = $queue->batchPeekMessage($numOfMessages); + $this->assertTrue($res->isSucceed()); + + $messages = $res->getMessages(); + $this->assertEquals($numOfMessages, count($messages)); + foreach ($messages as $message) + { + $this->assertEquals(strtoupper($bodyMD5), $message->getMessageBodyMD5()); + } + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $receiptHandles = array(); + $request = new BatchReceiveMessageRequest($numOfMessages); + try + { + $res = $queue->batchReceiveMessage($request); + $this->assertTrue($res->isSucceed()); + + $messages = $res->getMessages(); + $this->assertEquals($numOfMessages, count($messages)); + foreach ($messages as $message) + { + $this->assertEquals(strtoupper($bodyMD5), $message->getMessageBodyMD5()); + $receiptHandles[] = $message->getReceiptHandle(); + } + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $errorReceiptHandle = "1-ODU4OTkzNDU5My0xNDM1MTk3NjAwLTItNg=="; + $receiptHandles[] = $errorReceiptHandle; + try + { + $res = $queue->batchDeleteMessage($receiptHandles); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue($e instanceof BatchDeleteFailException); + $items = $e->getDeleteMessageErrorItems(); + $this->assertEquals(1, count($items)); + $this->assertEquals($errorReceiptHandle, $items[0]->getReceiptHandle()); + } + } +} + +?> diff --git a/Lib/Alisms/Tests/TopicTest.php b/Lib/Alisms/Tests/TopicTest.php new file mode 100644 index 0000000..3b158e5 --- /dev/null +++ b/Lib/Alisms/Tests/TopicTest.php @@ -0,0 +1,522 @@ +endPoint = $ini_array["endpoint"]; + $this->accessId = $ini_array["accessid"]; + $this->accessKey = $ini_array["accesskey"]; + + $this->topicToDelete = array(); + + $this->client = new Client($this->endPoint, $this->accessId, $this->accessKey); + } + + public function tearDown() + { + foreach ($this->topicToDelete as $topicName) + { + try + { + $this->client->deleteTopic($topicName); + } + catch (\Exception $e) + { + } + } + } + + private function prepareTopic($topicName, $attributes = NULL) + { + $request = new CreateTopicRequest($topicName, $attributes); + $this->topicToDelete[] = $topicName; + try + { + $res = $this->client->createTopic($request); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + return $this->client->getTopicRef($topicName); + } + + private function prepareSubscription(Topic $topic, $subscriptionName) + { + try + { + $attributes = new SubscriptionAttributes($subscriptionName, 'http://127.0.0.1', 'BACKOFF_RETRY', 'XML'); + $topic->subscribe($attributes); + } + catch (MnsException $e) + { + } + } + + public function testLoggingEnabled() + { + $topicName = "testLoggingEnabled"; + $topic = $this->prepareTopic($topicName); + + try + { + $attributes = new TopicAttributes; + $attributes->setLoggingEnabled(false); + $topic->setAttribute($attributes); + $res = $topic->getAttribute(); + $this->assertTrue($res->isSucceed()); + $this->assertEquals(false, $res->getTopicAttributes()->getLoggingEnabled()); + + $attributes = new TopicAttributes; + $attributes->setLoggingEnabled(true); + $topic->setAttribute($attributes); + $res = $topic->getAttribute(); + $this->assertTrue($res->isSucceed()); + $this->assertEquals(true, $res->getTopicAttributes()->getLoggingEnabled()); + + $attributes = new TopicAttributes; + $topic->setAttribute($attributes); + $res = $topic->getAttribute(); + $this->assertTrue($res->isSucceed()); + $this->assertEquals(true, $res->getTopicAttributes()->getLoggingEnabled()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + } + + public function testTopicAttributes() + { + $topicName = "testTopicAttributes"; + $topic = $this->prepareTopic($topicName); + + try + { + $res = $topic->getAttribute(); + $this->assertTrue($res->isSucceed()); + $this->assertEquals($topicName, $res->getTopicAttributes()->getTopicName()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $maximumMessageSize = 10 * 1024; + $attributes = new TopicAttributes; + $attributes->setMaximumMessageSize($maximumMessageSize); + try + { + $res = $topic->setAttribute($attributes); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + try + { + $res = $topic->getAttribute(); + $this->assertTrue($res->isSucceed()); + $this->assertEquals($res->getTopicAttributes()->getMaximumMessageSize(), $maximumMessageSize); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $this->client->deleteTopic($topicName); + + try + { + $res = $topic->getAttribute(); + $this->assertTrue(False, "Should throw TopicNotExistException"); + } + catch (MnsException $e) + { + $this->assertEquals($e->getMnsErrorCode(), Constants::TOPIC_NOT_EXIST); + } + + try + { + $res = $topic->setAttribute($attributes); + $this->assertTrue(False, "Should throw TopicNotExistException"); + } + catch (MnsException $e) + { + $this->assertEquals($e->getMnsErrorCode(), Constants::TOPIC_NOT_EXIST); + } + } + + public function testPublishMessage() + { + $topicName = "testPublishMessage" . uniqid(); + + $messageBody = "test"; + $bodyMD5 = md5($messageBody); + $request = new PublishMessageRequest($messageBody); + + $topic = $this->prepareTopic($topicName); + try + { + $res = $topic->publishMessage($request); + $this->assertTrue($res->isSucceed()); + $this->assertEquals(strtoupper($bodyMD5), $res->getMessageBodyMD5()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $this->client->deleteTopic($topic->getTopicName()); + try + { + $res = $topic->publishMessage($request); + $this->assertTrue(False, "Should throw TopicNotExistException"); + } + catch (MnsException $e) + { + $this->assertEquals($e->getMnsErrorCode(), Constants::TOPIC_NOT_EXIST); + } + } + + public function testPublishBatchSmsMessage() + { + $topicName = "testPublishBatchSmsMessage" . uniqid(); + + // now sub and send message + $messageBody = "test"; + $bodyMD5 = md5($messageBody); + + $topic = $this->prepareTopic($topicName); + try + { + $smsEndpoint = $topic->generateSmsEndpoint(); + + $subscriptionName = 'testSubscribeSubscription' . uniqid(); + $attributes = new SubscriptionAttributes($subscriptionName, $smsEndpoint); + $topic->subscribe($attributes); + + $batchSmsAttributes = new BatchSmsAttributes("陈舟锋", "SMS_15535414"); + $batchSmsAttributes->addReceiver("13735576932", array("name" => "phpsdk-batchsms")); + $messageAttributes = new MessageAttributes(array($batchSmsAttributes)); + $request = new PublishMessageRequest($messageBody, $messageAttributes); + + $res = $topic->publishMessage($request); + $this->assertTrue($res->isSucceed()); + $this->assertEquals(strtoupper($bodyMD5), $res->getMessageBodyMD5()); + echo $res->getMessageId(); + sleep(5); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $this->client->deleteTopic($topic->getTopicName()); + } + + public function testPublishDirectSmsMessage() + { + $topicName = "testPublishDirectSmsMessage" . uniqid(); + + // now sub and send message + $messageBody = "test"; + $bodyMD5 = md5($messageBody); + + $topic = $this->prepareTopic($topicName); + try + { + $smsEndpoint = $topic->generateSmsEndpoint(); + + $subscriptionName = 'testSubscribeSubscription' . uniqid(); + $attributes = new SubscriptionAttributes($subscriptionName, $smsEndpoint); + $topic->subscribe($attributes); + + $smsParams = array("name" => "phpsdk"); + $smsAttributes = new SmsAttributes("陈舟锋", "SMS_15535414", $smsParams, "13735576932"); + $messageAttributes = new MessageAttributes($smsAttributes); + $request = new PublishMessageRequest($messageBody, $messageAttributes); + + $res = $topic->publishMessage($request); + $this->assertTrue($res->isSucceed()); + $this->assertEquals(strtoupper($bodyMD5), $res->getMessageBodyMD5()); + echo $res->getMessageId(); + sleep(5); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $this->client->deleteTopic($topic->getTopicName()); + } + + public function testPublishMailMessage() + { + $topicName = "testPublishMailMessage" . uniqid(); + + // now sub and send message + $messageBody = "test"; + $bodyMD5 = md5($messageBody); + + $topic = $this->prepareTopic($topicName); + try + { + $mailEndpoint = $topic->generateMailEndpoint("liji.canglj@alibaba-inc.com"); + + $subscriptionName = 'testSubscribeSubscription' . uniqid(); + $attributes = new SubscriptionAttributes($subscriptionName, $mailEndpoint); + $topic->subscribe($attributes); + + $mailAttributes = new MailAttributes("TestSubject", "TestAccountName"); + $messageAttributes = new MessageAttributes($mailAttributes); + $request = new PublishMessageRequest($messageBody, $messageAttributes); + + $res = $topic->publishMessage($request); + $this->assertTrue($res->isSucceed()); + $this->assertEquals(strtoupper($bodyMD5), $res->getMessageBodyMD5()); + echo $res->getMessageId(); + sleep(5); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $this->client->deleteTopic($topic->getTopicName()); + } + + public function testPublishQueueMessage() + { + $topicName = "testPublishQueueMessage" . uniqid(); + + // prepare the queue + $queueName = "testPublishQueueMessageQueue"; + $this->client->deleteQueue($queueName); + $request = new CreateQueueRequest($queueName); + $this->client->createQueue($request); + + // now sub and send message + $messageBody = "test"; + $bodyMD5 = md5($messageBody); + + $topic = $this->prepareTopic($topicName); + try + { + $queue = $this->client->getQueueRef($queueName, FALSE); + + $queueEndpoint = $topic->generateQueueEndpoint($queueName); + //echo($queueEndpoint); + + $subscriptionName = 'testSubscribeSubscription' . uniqid(); + $attributes = new SubscriptionAttributes($subscriptionName, $queueEndpoint); + $topic->subscribe($attributes); + + $request = new PublishMessageRequest($messageBody); + + $res = $topic->publishMessage($request); + $this->assertTrue($res->isSucceed()); + $this->assertEquals(strtoupper($bodyMD5), $res->getMessageBodyMD5()); + + $res = $queue->receiveMessage(30); + $this->assertTrue(strpos($res->getMessageBody(), "" . $messageBody . "") >= 0); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $this->client->deleteTopic($topic->getTopicName()); + $this->client->deleteQueue($queueName); + } + + public function testSubscribe() + { + $topicName = 'testSubscribeTopic' . uniqid(); + $topic = $this->prepareTopic($topicName); + + $subscriptionName = 'testSubscribeSubscription' . uniqid(); + $attributes = new SubscriptionAttributes($subscriptionName, 'http://127.0.0.1', 'BACKOFF_RETRY', 'XML'); + try + { + $topic->subscribe($attributes); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + try + { + $attributes->setContentFormat('SIMPLIFIED'); + $res = $topic->subscribe($attributes); + $this->assertTrue(False, "Should throw SubscriptionAlreadyExist"); + } + catch (MnsException $e) + { + $this->assertEquals($e->getMnsErrorCode(), Constants::SUBSCRIPTION_ALREADY_EXIST); + } + + $topic->unsubscribe($subscriptionName); + } + + public function testSubscriptionAttributes() + { + $topicName = "testSubscriptionAttributes" . uniqid(); + $subscriptionName = "testSubscriptionAttributes" . uniqid(); + $topic = $this->prepareTopic($topicName); + $this->prepareSubscription($topic, $subscriptionName); + + try + { + $res = $topic->getSubscriptionAttribute($subscriptionName); + $this->assertTrue($res->isSucceed()); + $this->assertEquals($topicName, $res->getSubscriptionAttributes()->getTopicName()); + $this->assertEquals('BACKOFF_RETRY', $res->getSubscriptionAttributes()->getStrategy()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $strategy = 'EXPONENTIAL_DECAY_RETRY'; + $attributes = new UpdateSubscriptionAttributes($subscriptionName); + $attributes->setStrategy($strategy); + try + { + $res = $topic->setSubscriptionAttribute($attributes); + $this->assertTrue($res->isSucceed()); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + try + { + $res = $topic->getSubscriptionAttribute($subscriptionName); + $this->assertTrue($res->isSucceed()); + $this->assertEquals($res->getSubscriptionAttributes()->getStrategy(), $strategy); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + + $topic->unsubscribe($subscriptionName); + + try + { + $res = $topic->getSubscriptionAttribute($subscriptionName); + $this->assertTrue(False, "Should throw SubscriptionNotExistException"); + } + catch (MnsException $e) + { + $this->assertEquals($e->getMnsErrorCode(), Constants::SUBSCRIPTION_NOT_EXIST); + } + + try + { + $res = $topic->setSubscriptionAttribute($attributes); + $this->assertTrue(False, "Should throw SubscriptionNotExistException"); + } + catch (MnsException $e) + { + $this->assertEquals($e->getMnsErrorCode(), Constants::SUBSCRIPTION_NOT_EXIST); + } + } + + public function testListSubscriptions() + { + $topicName = "testListSubscriptionsTopic" . uniqid(); + $subscriptionNamePrefix = uniqid(); + $subscriptionName1 = $subscriptionNamePrefix . "testListTopic1"; + $subscriptionName2 = $subscriptionNamePrefix . "testListTopic2"; + + // 1. create Topic and Subscriptions + $topic = $this->prepareTopic($topicName); + $this->prepareSubscription($topic, $subscriptionName1); + $this->prepareSubscription($topic, $subscriptionName2); + + // 2. list subscriptions + $subscriptionName1Found = FALSE; + $subscriptionName2Found = FALSE; + + $count = 0; + $marker = ''; + while ($count < 2) { + try + { + $res = $topic->listSubscription(1, $subscriptionNamePrefix, $marker); + $this->assertTrue($res->isSucceed()); + + $subscriptionNames = $res->getSubscriptionNames(); + foreach ($subscriptionNames as $subscriptionName) + { + if ($subscriptionName == $subscriptionName1) + { + $subscriptionName1Found = TRUE; + } + elseif ($subscriptionName == $subscriptionName2) + { + $subscriptionName2Found = TRUE; + } + else + { + $this->assertTrue(FALSE, $subscriptionName . " Should not be here."); + } + } + + if ($count > 0) + { + $this->assertTrue($res->isFinished(), implode(", ", $subscriptionNames)); + } + $marker = $res->getNextMarker(); + } + catch (MnsException $e) + { + $this->assertTrue(FALSE, $e); + } + $count += 1; + } + + $this->assertTrue($subscriptionName1Found, $subscriptionName1 . " Not Found!"); + $this->assertTrue($subscriptionName2Found, $subscriptionName2 . " Not Found!"); + } +} + +?> diff --git a/Lib/Alisms/Tests/aliyun-mns.ini b/Lib/Alisms/Tests/aliyun-mns.ini new file mode 100644 index 0000000..2dcc3e1 --- /dev/null +++ b/Lib/Alisms/Tests/aliyun-mns.ini @@ -0,0 +1,3 @@ +endpoint = +accessid = +accesskey = diff --git a/Lib/Alisms/mns-autoloader.php b/Lib/Alisms/mns-autoloader.php new file mode 100644 index 0000000..b274187 --- /dev/null +++ b/Lib/Alisms/mns-autoloader.php @@ -0,0 +1,183 @@ + __DIR__ . '/AliyunMNS/Client.php', + 'AliyunMNS\Config' => __DIR__ . '/AliyunMNS/Config.php', + 'AliyunMNS\Constants' => __DIR__ . '/AliyunMNS/Constants.php', + 'AliyunMNS\Queue' => __DIR__ . '/AliyunMNS/Queue.php', + 'AliyunMNS\Topic' => __DIR__ . '/AliyunMNS/Topic.php', + 'AliyunMNS\AsyncCallback' => __DIR__ . '/AliyunMNS/AsyncCallback.php', + 'AliyunMNS\Common\XMLParser' => __DIR__ . '/AliyunMNS/Common/XMLParser.php', + 'AliyunMNS\Exception\MnsException' => __DIR__ . '/AliyunMNS/Exception/MnsException.php', + 'AliyunMNS\Exception\BatchDeleteFailException' => __DIR__ . '/AliyunMNS/Exception/BatchDeleteFailException.php', + 'AliyunMNS\Exception\InvalidArgumentException' => __DIR__ . '/AliyunMNS/Exception/InvalidArgumentException.php', + 'AliyunMNS\Exception\MessageNotExistException' => __DIR__ . '/AliyunMNS/Exception/MessageNotExistException.php', + 'AliyunMNS\Exception\QueueAlreadyExistException' => __DIR__ . '/AliyunMNS/Exception/QueueAlreadyExistException.php', + 'AliyunMNS\Exception\ReceiptHandleErrorException' => __DIR__ . '/AliyunMNS/Exception/ReceiptHandleErrorException.php', + 'AliyunMNS\Exception\BatchSendFailException' => __DIR__ . '/AliyunMNS/Exception/BatchSendFailException.php', + 'AliyunMNS\Exception\MalformedXMLException' => __DIR__ . '/AliyunMNS/Exception/MalformedXMLException.php', + 'AliyunMNS\Exception\QueueNotExistException' => __DIR__ . '/AliyunMNS/Exception/QueueNotExistException.php', + 'AliyunMNS\Exception\TopicAlreadyExistException' => __DIR__ . '/AliyunMNS/Exception/TopicAlreadyExistException.php', + 'AliyunMNS\Exception\TopicNotExistException' => __DIR__ . '/AliyunMNS/Exception/TopicNotExistException.php', + 'AliyunMNS\Exception\SubscriptionAlreadyExistException' => __DIR__ . '/AliyunMNS/Exception/SubscriptionAlreadyExistException.php', + 'AliyunMNS\Exception\SubscriptionNotExistException' => __DIR__ . '/AliyunMNS/Exception/SubscriptionNotExistException.php', + 'AliyunMNS\Http\HttpClient' => __DIR__ . '/AliyunMNS/Http/HttpClient.php', + 'AliyunMNS\Model\DeleteMessageErrorItem' => __DIR__ . '/AliyunMNS/Model/DeleteMessageErrorItem.php', + 'AliyunMNS\Model\Message' => __DIR__ . '/AliyunMNS/Model/Message.php', + 'AliyunMNS\Model\QueueAttributes' => __DIR__ . '/AliyunMNS/Model/QueueAttributes.php', + 'AliyunMNS\Model\MessageAttributes' => __DIR__ . '/AliyunMNS/Model/MessageAttributes.php', + 'AliyunMNS\Model\MailAttributes' => __DIR__ . '/AliyunMNS/Model/MailAttributes.php', + 'AliyunMNS\Model\SmsAttributes' => __DIR__ . '/AliyunMNS/Model/SmsAttributes.php', + 'AliyunMNS\Model\BatchSmsAttributes' => __DIR__ . '/AliyunMNS/Model/BatchSmsAttributes.php', + 'AliyunMNS\Model\WebSocketAttributes' => __DIR__ . '/AliyunMNS/Model/WebSocketAttributes.php', + 'AliyunMNS\Model\TopicAttributes' => __DIR__ . '/AliyunMNS/Model/TopicAttributes.php', + 'AliyunMNS\Model\AccountAttributes' => __DIR__ . '/AliyunMNS/Model/AccountAttributes.php', + 'AliyunMNS\Model\SubscriptionAttributes' => __DIR__ . '/AliyunMNS/Model/SubscriptionAttributes.php', + 'AliyunMNS\Model\UpdateSubscriptionAttributes' => __DIR__ . '/AliyunMNS/Model/UpdateSubscriptionAttributes.php', + 'AliyunMNS\Model\SendMessageRequestItem' => __DIR__ . '/AliyunMNS/Model/SendMessageRequestItem.php', + 'AliyunMNS\Model\SendMessageResponseItem' => __DIR__ . '/AliyunMNS/Model/SendMessageResponseItem.php', + 'AliyunMNS\Signature\Signature' => __DIR__ . '/AliyunMNS/Signature/Signature.php', + 'AliyunMNS\Traits\MessageIdAndMD5' => __DIR__ . '/AliyunMNS/Traits/MessageIdAndMD5.php', + 'AliyunMNS\Traits\MessagePropertiesForPeek' => __DIR__ . '/AliyunMNS/Traits/MessagePropertiesForPeek.php', + 'AliyunMNS\Traits\MessagePropertiesForReceive' => __DIR__ . '/AliyunMNS/Traits/MessagePropertiesForReceive.php', + 'AliyunMNS\Traits\MessagePropertiesForSend' => __DIR__ . '/AliyunMNS/Traits/MessagePropertiesForSend.php', + 'AliyunMNS\Traits\MessagePropertiesForPublish' => __DIR__ . '/AliyunMNS/Traits/MessagePropertiesForPublish.php', + 'AliyunMNS\Requests\BaseRequest' => __DIR__ . '/AliyunMNS/Requests/BaseRequest.php', + 'AliyunMNS\Requests\BatchDeleteMessageRequest' => __DIR__ . '/AliyunMNS/Requests/BatchDeleteMessageRequest.php', + 'AliyunMNS\Requests\BatchPeekMessageRequest' => __DIR__ . '/AliyunMNS/Requests/BatchPeekMessageRequest.php', + 'AliyunMNS\Requests\BatchReceiveMessageRequest' => __DIR__ . '/AliyunMNS/Requests/BatchReceiveMessageRequest.php', + 'AliyunMNS\Requests\BatchSendMessageRequest' => __DIR__ . '/AliyunMNS/Requests/BatchSendMessageRequest.php', + 'AliyunMNS\Requests\ChangeMessageVisibilityRequest' => __DIR__ . '/AliyunMNS/Requests/ChangeMessageVisibilityRequest.php', + 'AliyunMNS\Requests\CreateQueueRequest' => __DIR__ . '/AliyunMNS/Requests/CreateQueueRequest.php', + 'AliyunMNS\Requests\DeleteMessageRequest' => __DIR__ . '/AliyunMNS/Requests/DeleteMessageRequest.php', + 'AliyunMNS\Requests\DeleteQueueRequest' => __DIR__ . '/AliyunMNS/Requests/DeleteQueueRequest.php', + 'AliyunMNS\Requests\GetQueueAttributeRequest' => __DIR__ . '/AliyunMNS/Requests/GetQueueAttributeRequest.php', + 'AliyunMNS\Requests\GetAccountAttributesRequest' => __DIR__ . '/AliyunMNS/Requests/GetAccountAttributesRequest.php', + 'AliyunMNS\Requests\ListQueueRequest' => __DIR__ . '/AliyunMNS/Requests/ListQueueRequest.php', + 'AliyunMNS\Requests\PeekMessageRequest' => __DIR__ . '/AliyunMNS/Requests/PeekMessageRequest.php', + 'AliyunMNS\Requests\ReceiveMessageRequest' => __DIR__ . '/AliyunMNS/Requests/ReceiveMessageRequest.php', + 'AliyunMNS\Requests\SendMessageRequest' => __DIR__ . '/AliyunMNS/Requests/SendMessageRequest.php', + 'AliyunMNS\Requests\SetQueueAttributeRequest' => __DIR__ . '/AliyunMNS/Requests/SetQueueAttributeRequest.php', + 'AliyunMNS\Requests\SetAccountAttributesRequest' => __DIR__ . '/AliyunMNS/Requests/SetAccountAttributesRequest.php', + 'AliyunMNS\Requests\CreateTopicRequest' => __DIR__ . '/AliyunMNS/Requests/CreateTopicRequest.php', + 'AliyunMNS\Requests\DeleteTopicRequest' => __DIR__ . '/AliyunMNS/Requests/DeleteTopicRequest.php', + 'AliyunMNS\Requests\ListTopicRequest' => __DIR__ . '/AliyunMNS/Requests/ListTopicRequest.php', + 'AliyunMNS\Requests\GetTopicAttributeRequest' => __DIR__ . '/AliyunMNS/Requests/GetTopicAttributeRequest.php', + 'AliyunMNS\Requests\SetTopicAttributeRequest' => __DIR__ . '/AliyunMNS/Requests/SetTopicAttributeRequest.php', + 'AliyunMNS\Requests\PublishMessageRequest' => __DIR__ . '/AliyunMNS/Requests/PublishMessageRequest.php', + 'AliyunMNS\Requests\SubscribeRequest' => __DIR__ . '/AliyunMNS/Requests/SubscribeRequest.php', + 'AliyunMNS\Requests\UnsubscribeRequest' => __DIR__ . '/AliyunMNS/Requests/UnsubscribeRequest.php', + 'AliyunMNS\Requests\GetSubscriptionAttributeRequest' => __DIR__ . '/AliyunMNS/Requests/GetSubscriptionAttributeRequest.php', + 'AliyunMNS\Requests\SetSubscriptionAttributeRequest' => __DIR__ . '/AliyunMNS/Requests/SetSubscriptionAttributeRequest.php', + 'AliyunMNS\Requests\ListSubscriptionRequest' => __DIR__ . '/AliyunMNS/Requests/ListSubscriptionRequest.php', + 'AliyunMNS\Responses\BaseResponse' => __DIR__ . '/AliyunMNS/Responses/BaseResponse.php', + 'AliyunMNS\Responses\BatchDeleteMessageResponse' => __DIR__ . '/AliyunMNS/Responses/BatchDeleteMessageResponse.php', + 'AliyunMNS\Responses\BatchPeekMessageResponse' => __DIR__ . '/AliyunMNS/Responses/BatchPeekMessageResponse.php', + 'AliyunMNS\Responses\BatchReceiveMessageResponse' => __DIR__ . '/AliyunMNS/Responses/BatchReceiveMessageResponse.php', + 'AliyunMNS\Responses\BatchSendMessageResponse' => __DIR__ . '/AliyunMNS/Responses/BatchSendMessageResponse.php', + 'AliyunMNS\Responses\ChangeMessageVisibilityResponse' => __DIR__ . '/AliyunMNS/Responses/ChangeMessageVisibilityResponse.php', + 'AliyunMNS\Responses\CreateQueueResponse' => __DIR__ . '/AliyunMNS/Responses/CreateQueueResponse.php', + 'AliyunMNS\Responses\DeleteMessageResponse' => __DIR__ . '/AliyunMNS/Responses/DeleteMessageResponse.php', + 'AliyunMNS\Responses\DeleteQueueResponse' => __DIR__ . '/AliyunMNS/Responses/DeleteQueueResponse.php', + 'AliyunMNS\Responses\GetQueueAttributeResponse' => __DIR__ . '/AliyunMNS/Responses/GetQueueAttributeResponse.php', + 'AliyunMNS\Responses\GetAccountAttributesResponse' => __DIR__ . '/AliyunMNS/Responses/GetAccountAttributesResponse.php', + 'AliyunMNS\Responses\ListQueueResponse' => __DIR__ . '/AliyunMNS/Responses/ListQueueResponse.php', + 'AliyunMNS\Responses\MnsPromise' => __DIR__ . '/AliyunMNS/Responses/MnsPromise.php', + 'AliyunMNS\Responses\PeekMessageResponse' => __DIR__ . '/AliyunMNS/Responses/PeekMessageResponse.php', + 'AliyunMNS\Responses\ReceiveMessageResponse' => __DIR__ . '/AliyunMNS/Responses/ReceiveMessageResponse.php', + 'AliyunMNS\Responses\SendMessageResponse' => __DIR__ . '/AliyunMNS/Responses/SendMessageResponse.php', + 'AliyunMNS\Responses\SetQueueAttributeResponse' => __DIR__ . '/AliyunMNS/Responses/SetQueueAttributeResponse.php', + 'AliyunMNS\Responses\SetAccountAttributesResponse' => __DIR__ . '/AliyunMNS/Responses/SetAccountAttributesResponse.php', + 'AliyunMNS\Responses\CreateTopicResponse' => __DIR__ . '/AliyunMNS/Responses/CreateTopicResponse.php', + 'AliyunMNS\Responses\DeleteTopicResponse' => __DIR__ . '/AliyunMNS/Responses/DeleteTopicResponse.php', + 'AliyunMNS\Responses\ListTopicResponse' => __DIR__ . '/AliyunMNS/Responses/ListTopicResponse.php', + 'AliyunMNS\Responses\GetTopicAttributeResponse' => __DIR__ . '/AliyunMNS/Responses/GetTopicAttributeResponse.php', + 'AliyunMNS\Responses\SetTopicAttributeResponse' => __DIR__ . '/AliyunMNS/Responses/SetTopicAttributeResponse.php', + 'AliyunMNS\Responses\PublishMessageResponse' => __DIR__ . '/AliyunMNS/Responses/PublishMessageResponse.php', + 'AliyunMNS\Responses\SubscribeResponse' => __DIR__ . '/AliyunMNS/Responses/SubscribeResponse.php', + 'AliyunMNS\Responses\UnsubscribeResponse' => __DIR__ . '/AliyunMNS/Responses/UnsubscribeResponse.php', + 'AliyunMNS\Responses\GetSubscriptionAttributeResponse' => __DIR__ . '/AliyunMNS/Responses/GetSubscriptionAttributeResponse.php', + 'AliyunMNS\Responses\SetSubscriptionAttributeResponse' => __DIR__ . '/AliyunMNS/Responses/SetSubscriptionAttributeResponse.php', + 'AliyunMNS\Responses\ListSubscriptionResponse' => __DIR__ . '/AliyunMNS/Responses/ListSubscriptionResponse.php', + 'GuzzleHttp\Client' => __DIR__ . '/GuzzleHttp/Client.php', + 'GuzzleHttp\ClientInterface' => __DIR__ . '/GuzzleHttp/ClientInterface.php', + 'GuzzleHttp\Cookie\CookieJar' => __DIR__ . '/GuzzleHttp/Cookie/CookieJar.php', + 'GuzzleHttp\Cookie\CookieJarInterface' => __DIR__ . '/GuzzleHttp/Cookie/CookieJarInterface.php', + 'GuzzleHttp\Cookie\FileCookieJar' => __DIR__ . '/GuzzleHttp/Cookie/FileCookieJar.php', + 'GuzzleHttp\Cookie\SessionCookieJar' => __DIR__ . '/GuzzleHttp/Cookie/SessionCookieJar.php', + 'GuzzleHttp\Cookie\SetCookie' => __DIR__ . '/GuzzleHttp/Cookie/SetCookie.php', + 'GuzzleHttp\Exception\BadResponseException' => __DIR__ . '/GuzzleHttp/Exception/BadResponseException.php', + 'GuzzleHttp\Exception\ClientException' => __DIR__ . '/GuzzleHttp/Exception/ClientException.php', + 'GuzzleHttp\Exception\ConnectException' => __DIR__ . '/GuzzleHttp/Exception/ConnectException.php', + 'GuzzleHttp\Exception\GuzzleException' => __DIR__ . '/GuzzleHttp/Exception/GuzzleException.php', + 'GuzzleHttp\Exception\RequestException' => __DIR__ . '/GuzzleHttp/Exception/RequestException.php', + 'GuzzleHttp\Exception\SeekException' => __DIR__ . '/GuzzleHttp/Exception/SeekException.php', + 'GuzzleHttp\Exception\ServerException' => __DIR__ . '/GuzzleHttp/Exception/ServerException.php', + 'GuzzleHttp\Exception\TooManyRedirectsException' => __DIR__ . '/GuzzleHttp/Exception/TooManyRedirectsException.php', + 'GuzzleHttp\Exception\TransferException' => __DIR__ . '/GuzzleHttp/Exception/TransferException.php', + 'GuzzleHttp\functions' => __DIR__ . '/GuzzleHttp/functions.php', + 'GuzzleHttp\functions_include' => __DIR__ . '/GuzzleHttp/functions_include.php', + 'GuzzleHttp\Handler\CurlFactory' => __DIR__ . '/GuzzleHttp/Handler/CurlFactory.php', + 'GuzzleHttp\Handler\CurlFactoryInterface' => __DIR__ . '/GuzzleHttp/Handler/CurlFactoryInterface.php', + 'GuzzleHttp\Handler\CurlHandler' => __DIR__ . '/GuzzleHttp/Handler/CurlHandler.php', + 'GuzzleHttp\Handler\CurlMultiHandler' => __DIR__ . '/GuzzleHttp/Handler/CurlMultiHandler.php', + 'GuzzleHttp\Handler\EasyHandle' => __DIR__ . '/GuzzleHttp/Handler/EasyHandle.php', + 'GuzzleHttp\Handler\MockHandler' => __DIR__ . '/GuzzleHttp/Handler/MockHandler.php', + 'GuzzleHttp\Handler\Proxy' => __DIR__ . '/GuzzleHttp/Handler/Proxy.php', + 'GuzzleHttp\Handler\StreamHandler' => __DIR__ . '/GuzzleHttp/Handler/StreamHandler.php', + 'GuzzleHttp\HandlerStack' => __DIR__ . '/GuzzleHttp/HandlerStack.php', + 'GuzzleHttp\MessageFormatter' => __DIR__ . '/GuzzleHttp/MessageFormatter.php', + 'GuzzleHttp\Middleware' => __DIR__ . '/GuzzleHttp/Middleware.php', + 'GuzzleHttp\Pool' => __DIR__ . '/GuzzleHttp/Pool.php', + 'GuzzleHttp\PrepareBodyMiddleware' => __DIR__ . '/GuzzleHttp/PrepareBodyMiddleware.php', + 'GuzzleHttp\Promise\AggregateException' => __DIR__ . '/GuzzleHttp/Promise/AggregateException.php', + 'GuzzleHttp\Promise\CancellationException' => __DIR__ . '/GuzzleHttp/Promise/CancellationException.php', + 'GuzzleHttp\Promise\EachPromise' => __DIR__ . '/GuzzleHttp/Promise/EachPromise.php', + 'GuzzleHttp\Promise\FulfilledPromise' => __DIR__ . '/GuzzleHttp/Promise/FulfilledPromise.php', + 'GuzzleHttp\Promise\functions' => __DIR__ . '/GuzzleHttp/Promise/functions.php', + 'GuzzleHttp\Promise\functions_include' => __DIR__ . '/GuzzleHttp/Promise/functions_include.php', + 'GuzzleHttp\Promise\Promise' => __DIR__ . '/GuzzleHttp/Promise/Promise.php', + 'GuzzleHttp\Promise\PromiseInterface' => __DIR__ . '/GuzzleHttp/Promise/PromiseInterface.php', + 'GuzzleHttp\Promise\PromisorInterface' => __DIR__ . '/GuzzleHttp/Promise/PromisorInterface.php', + 'GuzzleHttp\Promise\RejectedPromise' => __DIR__ . '/GuzzleHttp/Promise/RejectedPromise.php', + 'GuzzleHttp\Promise\RejectionException' => __DIR__ . '/GuzzleHttp/Promise/RejectionException.php', + 'GuzzleHttp\Promise\TaskQueue' => __DIR__ . '/GuzzleHttp/Promise/TaskQueue.php', + 'GuzzleHttp\Psr7\AppendStream' => __DIR__ . '/GuzzleHttp/Psr7/AppendStream.php', + 'GuzzleHttp\Psr7\BufferStream' => __DIR__ . '/GuzzleHttp/Psr7/BufferStream.php', + 'GuzzleHttp\Psr7\CachingStream' => __DIR__ . '/GuzzleHttp/Psr7/CachingStream.php', + 'GuzzleHttp\Psr7\DroppingStream' => __DIR__ . '/GuzzleHttp/Psr7/DroppingStream.php', + 'GuzzleHttp\Psr7\FnStream' => __DIR__ . '/GuzzleHttp/Psr7/FnStream.php', + 'GuzzleHttp\Psr7\functions' => __DIR__ . '/GuzzleHttp/Psr7/functions.php', + 'GuzzleHttp\Psr7\functions_include' => __DIR__ . '/GuzzleHttp/Psr7/functions_include.php', + 'GuzzleHttp\Psr7\InflateStream' => __DIR__ . '/GuzzleHttp/Psr7/InflateStream.php', + 'GuzzleHttp\Psr7\LazyOpenStream' => __DIR__ . '/GuzzleHttp/Psr7/LazyOpenStream.php', + 'GuzzleHttp\Psr7\LimitStream' => __DIR__ . '/GuzzleHttp/Psr7/LimitStream.php', + 'GuzzleHttp\Psr7\MessageTrait' => __DIR__ . '/GuzzleHttp/Psr7/MessageTrait.php', + 'GuzzleHttp\Psr7\MultipartStream' => __DIR__ . '/GuzzleHttp/Psr7/MultipartStream.php', + 'GuzzleHttp\Psr7\NoSeekStream' => __DIR__ . '/GuzzleHttp/Psr7/NoSeekStream.php', + 'GuzzleHttp\Psr7\PumpStream' => __DIR__ . '/GuzzleHttp/Psr7/PumpStream.php', + 'GuzzleHttp\Psr7\Request' => __DIR__ . '/GuzzleHttp/Psr7/Request.php', + 'GuzzleHttp\Psr7\Response' => __DIR__ . '/GuzzleHttp/Psr7/Response.php', + 'GuzzleHttp\Psr7\Stream' => __DIR__ . '/GuzzleHttp/Psr7/Stream.php', + 'GuzzleHttp\Psr7\StreamDecoratorTrait' => __DIR__ . '/GuzzleHttp/Psr7/StreamDecoratorTrait.php', + 'GuzzleHttp\Psr7\StreamWrapper' => __DIR__ . '/GuzzleHttp/Psr7/StreamWrapper.php', + 'GuzzleHttp\Psr7\Uri' => __DIR__ . '/GuzzleHttp/Psr7/Uri.php', + 'GuzzleHttp\RedirectMiddleware' => __DIR__ . '/GuzzleHttp/RedirectMiddleware.php', + 'GuzzleHttp\RequestOptions' => __DIR__ . '/GuzzleHttp/RequestOptions.php', + 'GuzzleHttp\RetryMiddleware' => __DIR__ . '/GuzzleHttp/RetryMiddleware.php', + 'GuzzleHttp\TransferStats' => __DIR__ . '/GuzzleHttp/TransferStats.php', + 'GuzzleHttp\UriTemplate' => __DIR__ . '/GuzzleHttp/UriTemplate.php', + 'Psr\Http\Message\MessageInterface' => __DIR__ . '/Psr/Http/Message/MessageInterface.php', + 'Psr\Http\Message\RequestInterface' => __DIR__ . '/Psr/Http/Message/RequestInterface.php', + 'Psr\Http\Message\ResponseInterface' => __DIR__ . '/Psr/Http/Message/ResponseInterface.php', + 'Psr\Http\Message\ServerRequestInterface' => __DIR__ . '/Psr/Http/Message/ServerRequestInterface.php', + 'Psr\Http\Message\StreamInterface' => __DIR__ . '/Psr/Http/Message/StreamInterface.php', + 'Psr\Http\Message\UploadedFileInterface' => __DIR__ . '/Psr/Http/Message/UploadedFileInterface.php', + 'Psr\Http\Message\UriInterface' => __DIR__ . '/Psr/Http/Message/UriInterface.php', +); + +spl_autoload_register(function ($class) use ($mapping) { + if (isset($mapping[$class])) { + require $mapping[$class]; + } +}, true);