File indexing completed on 2025-01-26 05:25:26
0001 <?php 0002 /** 0003 * Zend Framework 0004 * 0005 * LICENSE 0006 * 0007 * This source file is subject to the new BSD license that is bundled 0008 * with this package in the file LICENSE.txt. 0009 * It is also available through the world-wide-web at this URL: 0010 * http://framework.zend.com/license/new-bsd 0011 * If you did not receive a copy of the license and are unable to 0012 * obtain it through the world-wide-web, please send an email 0013 * to license@zend.com so we can send you a copy immediately. 0014 * 0015 * @category Zend 0016 * @package Zend_Service 0017 * @subpackage Amazon_Sqs 0018 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0019 * @license http://framework.zend.com/license/new-bsd New BSD License 0020 * @version $Id$ 0021 */ 0022 0023 /** 0024 * @see Zend_Service_Amazon_Abstract 0025 */ 0026 // require_once 'Zend/Service/Amazon/Abstract.php'; 0027 0028 /** 0029 * @see Zend_Crypt_Hmac 0030 */ 0031 // require_once 'Zend/Crypt/Hmac.php'; 0032 0033 /** 0034 * Class for connecting to the Amazon Simple Queue Service (SQS) 0035 * 0036 * @category Zend 0037 * @package Zend_Service 0038 * @subpackage Amazon_Sqs 0039 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0040 * @license http://framework.zend.com/license/new-bsd New BSD License 0041 * @see http://aws.amazon.com/sqs/ Amazon Simple Queue Service 0042 */ 0043 class Zend_Service_Amazon_Sqs extends Zend_Service_Amazon_Abstract 0044 { 0045 /** 0046 * Default timeout for createQueue() function 0047 */ 0048 const CREATE_TIMEOUT_DEFAULT = 30; 0049 0050 /** 0051 * HTTP end point for the Amazon SQS service 0052 */ 0053 protected $_sqsEndpoint = 'queue.amazonaws.com'; 0054 0055 /** 0056 * The API version to use 0057 */ 0058 protected $_sqsApiVersion = '2009-02-01'; 0059 0060 /** 0061 * Signature Version 0062 */ 0063 protected $_sqsSignatureVersion = '2'; 0064 0065 /** 0066 * Signature Encoding Method 0067 */ 0068 protected $_sqsSignatureMethod = 'HmacSHA256'; 0069 0070 protected $_sqsEndpoints = array('us-east-1' => 'sqs.us-east-1.amazonaws.com', 0071 'us-west-1' => 'sqs.us-west-1.amazonaws.com', 0072 'eu-west-1' => 'sqs.eu-west-1.amazonaws.com', 0073 'ap-southeast-1' => 'sqs.ap-southeast-1.amazonaws.com', 0074 'ap-northeast-1' => 'sqs.ap-northeast-1.amazonaws.com'); 0075 /** 0076 * Constructor 0077 * 0078 * The default region is us-east-1. Use the region to set it to one of the regions that is build-in into ZF. 0079 * To add a new AWS region use the setEndpoint() method. 0080 * 0081 * @param string $accessKey 0082 * @param string $secretKey 0083 * @param string $region 0084 */ 0085 public function __construct($accessKey = null, $secretKey = null, $region = null) 0086 { 0087 parent::__construct($accessKey, $secretKey, $region); 0088 0089 if (null !== $region) { 0090 $this->_setEndpoint($region); 0091 } 0092 } 0093 0094 /** 0095 * Set SQS endpoint 0096 * 0097 * Checks and sets endpoint if region exists in $_sqsEndpoints. If a new SQS region is added by amazon, 0098 * please use the setEndpoint function to set it. 0099 * 0100 * @param string $region region 0101 * @throws Zend_Service_Amazon_Sqs_Exception 0102 */ 0103 protected function _setEndpoint($region) 0104 { 0105 if (array_key_exists($region, $this->_sqsEndpoints)) { 0106 $this->_sqsEndpoint = $this->_sqsEndpoints[$region]; 0107 } else { 0108 throw new Zend_Service_Amazon_Sqs_Exception('Invalid SQS region specified.'); 0109 } 0110 } 0111 0112 /** 0113 * Set SQS endpoint 0114 * 0115 * You can set SQS to on of the build-in regions. If the region does not exsist it will be added. 0116 * 0117 * @param string $region region 0118 * @throws Zend_Service_Amazon_Sqs_Exception 0119 */ 0120 public function setEndpoint($region) 0121 { 0122 if (!empty($region)) { 0123 if (array_key_exists($region, $this->_sqsEndpoints)) { 0124 $this->_sqsEndpoint = $this->_sqsEndpoints[$region]; 0125 } else { 0126 $this->_sqsEndpoints[$region] = "sqs.$region.amazonaws.com"; 0127 $this->_sqsEndpoint = $this->_sqsEndpoints[$region]; 0128 } 0129 } else { 0130 throw new Zend_Service_Amazon_Sqs_Exception('Empty region specified.'); 0131 } 0132 } 0133 0134 /** 0135 * Get the SQS endpoint 0136 * 0137 * @return string 0138 */ 0139 public function getEndpoint() 0140 { 0141 return $this->_sqsEndpoint; 0142 } 0143 0144 /** 0145 * Get possible SQS endpoints 0146 * 0147 * Since there is not an SQS webserive to get all possible endpoints, a hardcoded list is available. 0148 * For the actual region list please check: 0149 * http://docs.amazonwebservices.com/AWSSimpleQueueService/2009-02-01/APIReference/index.html?QueueServiceWsdlArticle.html 0150 * 0151 * @param string $region region 0152 * @return array 0153 */ 0154 public function getEndpoints() 0155 { 0156 return $this->_sqsEndpoints; 0157 } 0158 0159 /** 0160 * Create a new queue 0161 * 0162 * Visibility timeout is how long a message is left in the queue "invisible" 0163 * to other readers. If the message is acknowleged (deleted) before the 0164 * timeout, then the message is deleted. However, if the timeout expires 0165 * then the message will be made available to other queue readers. 0166 * 0167 * @param string $queue_name queue name 0168 * @param integer $timeout default visibility timeout 0169 * @return string|boolean 0170 * @throws Zend_Service_Amazon_Sqs_Exception 0171 */ 0172 public function create($queue_name, $timeout = null) 0173 { 0174 $params = array(); 0175 $params['QueueName'] = $queue_name; 0176 $timeout = ($timeout === null) ? self::CREATE_TIMEOUT_DEFAULT : (int)$timeout; 0177 $params['DefaultVisibilityTimeout'] = $timeout; 0178 0179 $retry_count = 0; 0180 0181 do { 0182 $retry = false; 0183 $result = $this->_makeRequest(null, 'CreateQueue', $params); 0184 0185 if (!isset($result->CreateQueueResult->QueueUrl) 0186 || empty($result->CreateQueueResult->QueueUrl) 0187 ) { 0188 if ($result->Error->Code == 'AWS.SimpleQueueService.QueueNameExists') { 0189 return false; 0190 } elseif ($result->Error->Code == 'AWS.SimpleQueueService.QueueDeletedRecently') { 0191 // Must sleep for 60 seconds, then try re-creating the queue 0192 sleep(60); 0193 $retry = true; 0194 $retry_count++; 0195 } else { 0196 // require_once 'Zend/Service/Amazon/Sqs/Exception.php'; 0197 throw new Zend_Service_Amazon_Sqs_Exception($result->Error->Code); 0198 } 0199 } else { 0200 return (string) $result->CreateQueueResult->QueueUrl; 0201 } 0202 0203 } while ($retry); 0204 0205 return false; 0206 } 0207 0208 /** 0209 * Delete a queue and all of it's messages 0210 * 0211 * Returns false if the queue is not found, true if the queue exists 0212 * 0213 * @param string $queue_url queue URL 0214 * @return boolean 0215 * @throws Zend_Service_Amazon_Sqs_Exception 0216 */ 0217 public function delete($queue_url) 0218 { 0219 $result = $this->_makeRequest($queue_url, 'DeleteQueue'); 0220 0221 if ($result->Error->Code !== null) { 0222 // require_once 'Zend/Service/Amazon/Sqs/Exception.php'; 0223 throw new Zend_Service_Amazon_Sqs_Exception($result->Error->Code); 0224 } 0225 0226 return true; 0227 } 0228 0229 /** 0230 * Get an array of all available queues 0231 * 0232 * @return array 0233 * @throws Zend_Service_Amazon_Sqs_Exception 0234 */ 0235 public function getQueues() 0236 { 0237 $result = $this->_makeRequest(null, 'ListQueues'); 0238 0239 if (isset($result->Error)) { 0240 // require_once 'Zend/Service/Amazon/Sqs/Exception.php'; 0241 throw new Zend_Service_Amazon_Sqs_Exception($result->Error->Code); 0242 } 0243 0244 if (!isset($result->ListQueuesResult->QueueUrl) 0245 || empty($result->ListQueuesResult->QueueUrl) 0246 ) { 0247 return array(); 0248 } 0249 0250 $queues = array(); 0251 foreach ($result->ListQueuesResult->QueueUrl as $queue_url) { 0252 $queues[] = (string)$queue_url; 0253 } 0254 0255 return $queues; 0256 } 0257 0258 /** 0259 * Return the approximate number of messages in the queue 0260 * 0261 * @param string $queue_url Queue URL 0262 * @return integer 0263 * @throws Zend_Service_Amazon_Sqs_Exception 0264 */ 0265 public function count($queue_url) 0266 { 0267 return (int)$this->getAttribute($queue_url, 'ApproximateNumberOfMessages'); 0268 } 0269 0270 /** 0271 * Send a message to the queue 0272 * 0273 * @param string $queue_url Queue URL 0274 * @param string $message Message to send to the queue 0275 * @return string Message ID 0276 * @throws Zend_Service_Amazon_Sqs_Exception 0277 */ 0278 public function send($queue_url, $message) 0279 { 0280 $params = array(); 0281 $params['MessageBody'] = urlencode($message); 0282 0283 $checksum = md5($params['MessageBody']); 0284 0285 $result = $this->_makeRequest($queue_url, 'SendMessage', $params); 0286 0287 if (!isset($result->SendMessageResult->MessageId) 0288 || empty($result->SendMessageResult->MessageId) 0289 ) { 0290 // require_once 'Zend/Service/Amazon/Sqs/Exception.php'; 0291 throw new Zend_Service_Amazon_Sqs_Exception($result->Error->Code); 0292 } else if ((string) $result->SendMessageResult->MD5OfMessageBody != $checksum) { 0293 // require_once 'Zend/Service/Amazon/Sqs/Exception.php'; 0294 throw new Zend_Service_Amazon_Sqs_Exception('MD5 of body does not match message sent'); 0295 } 0296 0297 return (string) $result->SendMessageResult->MessageId; 0298 } 0299 0300 /** 0301 * Get messages in the queue 0302 * 0303 * @param string $queue_url Queue name 0304 * @param integer $max_messages Maximum number of messages to return 0305 * @param integer $timeout Visibility timeout for these messages 0306 * @return array 0307 * @throws Zend_Service_Amazon_Sqs_Exception 0308 */ 0309 public function receive($queue_url, $max_messages = null, $timeout = null) 0310 { 0311 $params = array(); 0312 0313 // If not set, the visibility timeout on the queue is used 0314 if ($timeout !== null) { 0315 $params['VisibilityTimeout'] = (int)$timeout; 0316 } 0317 0318 // SQS will default to only returning one message 0319 if ($max_messages !== null) { 0320 $params['MaxNumberOfMessages'] = (int)$max_messages; 0321 } 0322 0323 $result = $this->_makeRequest($queue_url, 'ReceiveMessage', $params); 0324 0325 if (isset($result->Error)) { 0326 // require_once 'Zend/Service/Amazon/Sqs/Exception.php'; 0327 throw new Zend_Service_Amazon_Sqs_Exception($result->Error->Code); 0328 } 0329 0330 if (!isset($result->ReceiveMessageResult->Message) 0331 || empty($result->ReceiveMessageResult->Message) 0332 ) { 0333 // no messages found 0334 return array(); 0335 } 0336 0337 $data = array(); 0338 foreach ($result->ReceiveMessageResult->Message as $message) { 0339 $data[] = array( 0340 'message_id' => (string)$message->MessageId, 0341 'handle' => (string)$message->ReceiptHandle, 0342 'md5' => (string)$message->MD5OfBody, 0343 'body' => urldecode((string)$message->Body), 0344 ); 0345 } 0346 0347 return $data; 0348 } 0349 0350 /** 0351 * Delete a message from the queue 0352 * 0353 * Returns true if the message is deleted, false if the deletion is 0354 * unsuccessful. 0355 * 0356 * @param string $queue_url Queue URL 0357 * @param string $handle Message handle as returned by SQS 0358 * @return boolean 0359 * @throws Zend_Service_Amazon_Sqs_Exception 0360 */ 0361 public function deleteMessage($queue_url, $handle) 0362 { 0363 $params = array(); 0364 $params['ReceiptHandle'] = (string)$handle; 0365 0366 $result = $this->_makeRequest($queue_url, 'DeleteMessage', $params); 0367 0368 if (isset($result->Error->Code) 0369 && !empty($result->Error->Code) 0370 ) { 0371 return false; 0372 } 0373 0374 // Will always return true unless ReceiptHandle is malformed 0375 return true; 0376 } 0377 0378 /** 0379 * Get the attributes for the queue 0380 * 0381 * @param string $queue_url Queue URL 0382 * @param string $attribute 0383 * @return string 0384 * @throws Zend_Service_Amazon_Sqs_Exception 0385 */ 0386 public function getAttribute($queue_url, $attribute = 'All') 0387 { 0388 $params = array(); 0389 $params['AttributeName'] = $attribute; 0390 0391 $result = $this->_makeRequest($queue_url, 'GetQueueAttributes', $params); 0392 0393 if (!isset($result->GetQueueAttributesResult->Attribute) 0394 || empty($result->GetQueueAttributesResult->Attribute) 0395 ) { 0396 // require_once 'Zend/Service/Amazon/Sqs/Exception.php'; 0397 throw new Zend_Service_Amazon_Sqs_Exception($result->Error->Code); 0398 } 0399 0400 if(count($result->GetQueueAttributesResult->Attribute) > 1) { 0401 $attr_result = array(); 0402 foreach($result->GetQueueAttributesResult->Attribute as $attribute) { 0403 $attr_result[(string)$attribute->Name] = (string)$attribute->Value; 0404 } 0405 return $attr_result; 0406 } else { 0407 return (string) $result->GetQueueAttributesResult->Attribute->Value; 0408 } 0409 } 0410 0411 /** 0412 * Make a request to Amazon SQS 0413 * 0414 * @param string $queue Queue Name 0415 * @param string $action SQS action 0416 * @param array $params 0417 * @return SimpleXMLElement 0418 */ 0419 private function _makeRequest($queue_url, $action, $params = array()) 0420 { 0421 $params['Action'] = $action; 0422 $params = $this->addRequiredParameters($queue_url, $params); 0423 0424 if ($queue_url === null) { 0425 $queue_url = '/'; 0426 } 0427 0428 $client = self::getHttpClient(); 0429 0430 switch ($action) { 0431 case 'ListQueues': 0432 case 'CreateQueue': 0433 $client->setUri('http://'.$this->_sqsEndpoint); 0434 break; 0435 default: 0436 $client->setUri($queue_url); 0437 break; 0438 } 0439 0440 $retry_count = 0; 0441 0442 do { 0443 $retry = false; 0444 0445 $client->resetParameters(); 0446 $client->setParameterGet($params); 0447 0448 $response = $client->request('GET'); 0449 0450 $response_code = $response->getStatus(); 0451 0452 // Some 5xx errors are expected, so retry automatically 0453 if ($response_code >= 500 && $response_code < 600 && $retry_count <= 5) { 0454 $retry = true; 0455 $retry_count++; 0456 sleep($retry_count / 4 * $retry_count); 0457 } 0458 } while ($retry); 0459 0460 unset($client); 0461 0462 return new SimpleXMLElement($response->getBody()); 0463 } 0464 0465 /** 0466 * Adds required authentication and version parameters to an array of 0467 * parameters 0468 * 0469 * The required parameters are: 0470 * - AWSAccessKey 0471 * - SignatureVersion 0472 * - Timestamp 0473 * - Version and 0474 * - Signature 0475 * 0476 * If a required parameter is already set in the <tt>$parameters</tt> array, 0477 * it is overwritten. 0478 * 0479 * @param string $queue_url Queue URL 0480 * @param array $parameters the array to which to add the required 0481 * parameters. 0482 * @return array 0483 */ 0484 protected function addRequiredParameters($queue_url, array $parameters) 0485 { 0486 $parameters['AWSAccessKeyId'] = $this->_getAccessKey(); 0487 $parameters['SignatureVersion'] = $this->_sqsSignatureVersion; 0488 $parameters['Timestamp'] = gmdate('Y-m-d\TH:i:s\Z', time()+10); 0489 $parameters['Version'] = $this->_sqsApiVersion; 0490 $parameters['SignatureMethod'] = $this->_sqsSignatureMethod; 0491 $parameters['Signature'] = $this->_signParameters($queue_url, $parameters); 0492 0493 return $parameters; 0494 } 0495 0496 /** 0497 * Computes the RFC 2104-compliant HMAC signature for request parameters 0498 * 0499 * This implements the Amazon Web Services signature, as per the following 0500 * specification: 0501 * 0502 * 1. Sort all request parameters (including <tt>SignatureVersion</tt> and 0503 * excluding <tt>Signature</tt>, the value of which is being created), 0504 * ignoring case. 0505 * 0506 * 2. Iterate over the sorted list and append the parameter name (in its 0507 * original case) and then its value. Do not URL-encode the parameter 0508 * values before constructing this string. Do not use any separator 0509 * characters when appending strings. 0510 * 0511 * @param string $queue_url Queue URL 0512 * @param array $parameters the parameters for which to get the signature. 0513 * 0514 * @return string the signed data. 0515 */ 0516 protected function _signParameters($queue_url, array $paramaters) 0517 { 0518 $data = "GET\n"; 0519 $data .= $this->_sqsEndpoint . "\n"; 0520 if ($queue_url !== null) { 0521 $data .= parse_url($queue_url, PHP_URL_PATH); 0522 } 0523 else { 0524 $data .= '/'; 0525 } 0526 $data .= "\n"; 0527 0528 uksort($paramaters, 'strcmp'); 0529 unset($paramaters['Signature']); 0530 0531 $arrData = array(); 0532 foreach($paramaters as $key => $value) { 0533 $arrData[] = $key . '=' . str_replace('%7E', '~', urlencode($value)); 0534 } 0535 0536 $data .= implode('&', $arrData); 0537 0538 $hmac = Zend_Crypt_Hmac::compute($this->_getSecretKey(), 'SHA256', $data, Zend_Crypt_Hmac::BINARY); 0539 0540 return base64_encode($hmac); 0541 } 0542 }