PageRenderTime 37ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/Service/Amazon/Sqs.php

https://bitbucket.org/luizbrandaoj/mini-blog
PHP | 542 lines | 256 code | 69 blank | 217 comment | 43 complexity | d5fa713ea972d87a556b7c6381e296a8 MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Service
  17. * @subpackage Amazon_Sqs
  18. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id: Sqs.php 24470 2011-09-26 16:26:44Z ezimuel $
  21. */
  22. /**
  23. * @see Zend_Service_Amazon_Abstract
  24. */
  25. require_once 'Zend/Service/Amazon/Abstract.php';
  26. /**
  27. * @see Zend_Crypt_Hmac
  28. */
  29. require_once 'Zend/Crypt/Hmac.php';
  30. /**
  31. * Class for connecting to the Amazon Simple Queue Service (SQS)
  32. *
  33. * @category Zend
  34. * @package Zend_Service
  35. * @subpackage Amazon_Sqs
  36. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  37. * @license http://framework.zend.com/license/new-bsd New BSD License
  38. * @see http://aws.amazon.com/sqs/ Amazon Simple Queue Service
  39. */
  40. class Zend_Service_Amazon_Sqs extends Zend_Service_Amazon_Abstract
  41. {
  42. /**
  43. * Default timeout for createQueue() function
  44. */
  45. const CREATE_TIMEOUT_DEFAULT = 30;
  46. /**
  47. * HTTP end point for the Amazon SQS service
  48. */
  49. protected $_sqsEndpoint = 'queue.amazonaws.com';
  50. /**
  51. * The API version to use
  52. */
  53. protected $_sqsApiVersion = '2009-02-01';
  54. /**
  55. * Signature Version
  56. */
  57. protected $_sqsSignatureVersion = '2';
  58. /**
  59. * Signature Encoding Method
  60. */
  61. protected $_sqsSignatureMethod = 'HmacSHA256';
  62. protected $_sqsEndpoints = array('us-east-1' => 'sqs.us-east-1.amazonaws.com',
  63. 'us-west-1' => 'sqs.us-west-1.amazonaws.com',
  64. 'eu-west-1' => 'sqs.eu-west-1.amazonaws.com',
  65. 'ap-southeast-1' => 'sqs.ap-southeast-1.amazonaws.com',
  66. 'ap-northeast-1' => 'sqs.ap-northeast-1.amazonaws.com');
  67. /**
  68. * Constructor
  69. *
  70. * The default region is us-east-1. Use the region to set it to one of the regions that is build-in into ZF.
  71. * To add a new AWS region use the setEndpoint() method.
  72. *
  73. * @param string $accessKey
  74. * @param string $secretKey
  75. * @param string $region
  76. */
  77. public function __construct($accessKey = null, $secretKey = null, $region = null)
  78. {
  79. parent::__construct($accessKey, $secretKey, $region);
  80. if (null !== $region) {
  81. $this->_setEndpoint($region);
  82. }
  83. }
  84. /**
  85. * Set SQS endpoint
  86. *
  87. * Checks and sets endpoint if region exists in $_sqsEndpoints. If a new SQS region is added by amazon,
  88. * please use the setEndpoint function to set it.
  89. *
  90. * @param string $region region
  91. * @throws Zend_Service_Amazon_Sqs_Exception
  92. */
  93. protected function _setEndpoint($region)
  94. {
  95. if (array_key_exists($region, $this->_sqsEndpoints)) {
  96. $this->_sqsEndpoint = $this->_sqsEndpoints[$region];
  97. } else {
  98. throw new Zend_Service_Amazon_Sqs_Exception('Invalid SQS region specified.');
  99. }
  100. }
  101. /**
  102. * Set SQS endpoint
  103. *
  104. * You can set SQS to on of the build-in regions. If the region does not exsist it will be added.
  105. *
  106. * @param string $region region
  107. * @throws Zend_Service_Amazon_Sqs_Exception
  108. */
  109. public function setEndpoint($region)
  110. {
  111. if (!empty($region)) {
  112. if (array_key_exists($region, $this->_sqsEndpoints)) {
  113. $this->_sqsEndpoint = $this->_sqsEndpoints[$region];
  114. } else {
  115. $this->_sqsEndpoints[$region] = "sqs.$region.amazonaws.com";
  116. $this->_sqsEndpoint = $this->_sqsEndpoints[$region];
  117. }
  118. } else {
  119. throw new Zend_Service_Amazon_Sqs_Exception('Empty region specified.');
  120. }
  121. }
  122. /**
  123. * Get the SQS endpoint
  124. *
  125. * @return string
  126. */
  127. public function getEndpoint()
  128. {
  129. return $this->_sqsEndpoint;
  130. }
  131. /**
  132. * Get possible SQS endpoints
  133. *
  134. * Since there is not an SQS webserive to get all possible endpoints, a hardcoded list is available.
  135. * For the actual region list please check:
  136. * http://docs.amazonwebservices.com/AWSSimpleQueueService/2009-02-01/APIReference/index.html?QueueServiceWsdlArticle.html
  137. *
  138. * @param string $region region
  139. * @return array
  140. */
  141. public function getEndpoints()
  142. {
  143. return $this->_sqsEndpoints;
  144. }
  145. /**
  146. * Create a new queue
  147. *
  148. * Visibility timeout is how long a message is left in the queue "invisible"
  149. * to other readers. If the message is acknowleged (deleted) before the
  150. * timeout, then the message is deleted. However, if the timeout expires
  151. * then the message will be made available to other queue readers.
  152. *
  153. * @param string $queue_name queue name
  154. * @param integer $timeout default visibility timeout
  155. * @return string|boolean
  156. * @throws Zend_Service_Amazon_Sqs_Exception
  157. */
  158. public function create($queue_name, $timeout = null)
  159. {
  160. $params = array();
  161. $params['QueueName'] = $queue_name;
  162. $timeout = ($timeout === null) ? self::CREATE_TIMEOUT_DEFAULT : (int)$timeout;
  163. $params['DefaultVisibilityTimeout'] = $timeout;
  164. $retry_count = 0;
  165. do {
  166. $retry = false;
  167. $result = $this->_makeRequest(null, 'CreateQueue', $params);
  168. if (!isset($result->CreateQueueResult->QueueUrl)
  169. || empty($result->CreateQueueResult->QueueUrl)
  170. ) {
  171. if ($result->Error->Code == 'AWS.SimpleQueueService.QueueNameExists') {
  172. return false;
  173. } elseif ($result->Error->Code == 'AWS.SimpleQueueService.QueueDeletedRecently') {
  174. // Must sleep for 60 seconds, then try re-creating the queue
  175. sleep(60);
  176. $retry = true;
  177. $retry_count++;
  178. } else {
  179. require_once 'Zend/Service/Amazon/Sqs/Exception.php';
  180. throw new Zend_Service_Amazon_Sqs_Exception($result->Error->Code);
  181. }
  182. } else {
  183. return (string) $result->CreateQueueResult->QueueUrl;
  184. }
  185. } while ($retry);
  186. return false;
  187. }
  188. /**
  189. * Delete a queue and all of it's messages
  190. *
  191. * Returns false if the queue is not found, true if the queue exists
  192. *
  193. * @param string $queue_url queue URL
  194. * @return boolean
  195. * @throws Zend_Service_Amazon_Sqs_Exception
  196. */
  197. public function delete($queue_url)
  198. {
  199. $result = $this->_makeRequest($queue_url, 'DeleteQueue');
  200. if ($result->Error->Code !== null) {
  201. require_once 'Zend/Service/Amazon/Sqs/Exception.php';
  202. throw new Zend_Service_Amazon_Sqs_Exception($result->Error->Code);
  203. }
  204. return true;
  205. }
  206. /**
  207. * Get an array of all available queues
  208. *
  209. * @return array
  210. * @throws Zend_Service_Amazon_Sqs_Exception
  211. */
  212. public function getQueues()
  213. {
  214. $result = $this->_makeRequest(null, 'ListQueues');
  215. if (isset($result->Error)) {
  216. require_once 'Zend/Service/Amazon/Sqs/Exception.php';
  217. throw new Zend_Service_Amazon_Sqs_Exception($result->Error->Code);
  218. }
  219. if (!isset($result->ListQueuesResult->QueueUrl)
  220. || empty($result->ListQueuesResult->QueueUrl)
  221. ) {
  222. return array();
  223. }
  224. $queues = array();
  225. foreach ($result->ListQueuesResult->QueueUrl as $queue_url) {
  226. $queues[] = (string)$queue_url;
  227. }
  228. return $queues;
  229. }
  230. /**
  231. * Return the approximate number of messages in the queue
  232. *
  233. * @param string $queue_url Queue URL
  234. * @return integer
  235. * @throws Zend_Service_Amazon_Sqs_Exception
  236. */
  237. public function count($queue_url)
  238. {
  239. return (int)$this->getAttribute($queue_url, 'ApproximateNumberOfMessages');
  240. }
  241. /**
  242. * Send a message to the queue
  243. *
  244. * @param string $queue_url Queue URL
  245. * @param string $message Message to send to the queue
  246. * @return string Message ID
  247. * @throws Zend_Service_Amazon_Sqs_Exception
  248. */
  249. public function send($queue_url, $message)
  250. {
  251. $params = array();
  252. $params['MessageBody'] = urlencode($message);
  253. $checksum = md5($params['MessageBody']);
  254. $result = $this->_makeRequest($queue_url, 'SendMessage', $params);
  255. if (!isset($result->SendMessageResult->MessageId)
  256. || empty($result->SendMessageResult->MessageId)
  257. ) {
  258. require_once 'Zend/Service/Amazon/Sqs/Exception.php';
  259. throw new Zend_Service_Amazon_Sqs_Exception($result->Error->Code);
  260. } else if ((string) $result->SendMessageResult->MD5OfMessageBody != $checksum) {
  261. require_once 'Zend/Service/Amazon/Sqs/Exception.php';
  262. throw new Zend_Service_Amazon_Sqs_Exception('MD5 of body does not match message sent');
  263. }
  264. return (string) $result->SendMessageResult->MessageId;
  265. }
  266. /**
  267. * Get messages in the queue
  268. *
  269. * @param string $queue_url Queue name
  270. * @param integer $max_messages Maximum number of messages to return
  271. * @param integer $timeout Visibility timeout for these messages
  272. * @return array
  273. * @throws Zend_Service_Amazon_Sqs_Exception
  274. */
  275. public function receive($queue_url, $max_messages = null, $timeout = null)
  276. {
  277. $params = array();
  278. // If not set, the visibility timeout on the queue is used
  279. if ($timeout !== null) {
  280. $params['VisibilityTimeout'] = (int)$timeout;
  281. }
  282. // SQS will default to only returning one message
  283. if ($max_messages !== null) {
  284. $params['MaxNumberOfMessages'] = (int)$max_messages;
  285. }
  286. $result = $this->_makeRequest($queue_url, 'ReceiveMessage', $params);
  287. if (isset($result->Error)) {
  288. require_once 'Zend/Service/Amazon/Sqs/Exception.php';
  289. throw new Zend_Service_Amazon_Sqs_Exception($result->Error->Code);
  290. }
  291. if (!isset($result->ReceiveMessageResult->Message)
  292. || empty($result->ReceiveMessageResult->Message)
  293. ) {
  294. // no messages found
  295. return array();
  296. }
  297. $data = array();
  298. foreach ($result->ReceiveMessageResult->Message as $message) {
  299. $data[] = array(
  300. 'message_id' => (string)$message->MessageId,
  301. 'handle' => (string)$message->ReceiptHandle,
  302. 'md5' => (string)$message->MD5OfBody,
  303. 'body' => urldecode((string)$message->Body),
  304. );
  305. }
  306. return $data;
  307. }
  308. /**
  309. * Delete a message from the queue
  310. *
  311. * Returns true if the message is deleted, false if the deletion is
  312. * unsuccessful.
  313. *
  314. * @param string $queue_url Queue URL
  315. * @param string $handle Message handle as returned by SQS
  316. * @return boolean
  317. * @throws Zend_Service_Amazon_Sqs_Exception
  318. */
  319. public function deleteMessage($queue_url, $handle)
  320. {
  321. $params = array();
  322. $params['ReceiptHandle'] = (string)$handle;
  323. $result = $this->_makeRequest($queue_url, 'DeleteMessage', $params);
  324. if (isset($result->Error->Code)
  325. && !empty($result->Error->Code)
  326. ) {
  327. return false;
  328. }
  329. // Will always return true unless ReceiptHandle is malformed
  330. return true;
  331. }
  332. /**
  333. * Get the attributes for the queue
  334. *
  335. * @param string $queue_url Queue URL
  336. * @param string $attribute
  337. * @return string
  338. * @throws Zend_Service_Amazon_Sqs_Exception
  339. */
  340. public function getAttribute($queue_url, $attribute = 'All')
  341. {
  342. $params = array();
  343. $params['AttributeName'] = $attribute;
  344. $result = $this->_makeRequest($queue_url, 'GetQueueAttributes', $params);
  345. if (!isset($result->GetQueueAttributesResult->Attribute)
  346. || empty($result->GetQueueAttributesResult->Attribute)
  347. ) {
  348. require_once 'Zend/Service/Amazon/Sqs/Exception.php';
  349. throw new Zend_Service_Amazon_Sqs_Exception($result->Error->Code);
  350. }
  351. if(count($result->GetQueueAttributesResult->Attribute) > 1) {
  352. $attr_result = array();
  353. foreach($result->GetQueueAttributesResult->Attribute as $attribute) {
  354. $attr_result[(string)$attribute->Name] = (string)$attribute->Value;
  355. }
  356. return $attr_result;
  357. } else {
  358. return (string) $result->GetQueueAttributesResult->Attribute->Value;
  359. }
  360. }
  361. /**
  362. * Make a request to Amazon SQS
  363. *
  364. * @param string $queue Queue Name
  365. * @param string $action SQS action
  366. * @param array $params
  367. * @return SimpleXMLElement
  368. */
  369. private function _makeRequest($queue_url, $action, $params = array())
  370. {
  371. $params['Action'] = $action;
  372. $params = $this->addRequiredParameters($queue_url, $params);
  373. if ($queue_url === null) {
  374. $queue_url = '/';
  375. }
  376. $client = self::getHttpClient();
  377. switch ($action) {
  378. case 'ListQueues':
  379. case 'CreateQueue':
  380. $client->setUri('http://'.$this->_sqsEndpoint);
  381. break;
  382. default:
  383. $client->setUri($queue_url);
  384. break;
  385. }
  386. $retry_count = 0;
  387. do {
  388. $retry = false;
  389. $client->resetParameters();
  390. $client->setParameterGet($params);
  391. $response = $client->request('GET');
  392. $response_code = $response->getStatus();
  393. // Some 5xx errors are expected, so retry automatically
  394. if ($response_code >= 500 && $response_code < 600 && $retry_count <= 5) {
  395. $retry = true;
  396. $retry_count++;
  397. sleep($retry_count / 4 * $retry_count);
  398. }
  399. } while ($retry);
  400. unset($client);
  401. return new SimpleXMLElement($response->getBody());
  402. }
  403. /**
  404. * Adds required authentication and version parameters to an array of
  405. * parameters
  406. *
  407. * The required parameters are:
  408. * - AWSAccessKey
  409. * - SignatureVersion
  410. * - Timestamp
  411. * - Version and
  412. * - Signature
  413. *
  414. * If a required parameter is already set in the <tt>$parameters</tt> array,
  415. * it is overwritten.
  416. *
  417. * @param string $queue_url Queue URL
  418. * @param array $parameters the array to which to add the required
  419. * parameters.
  420. * @return array
  421. */
  422. protected function addRequiredParameters($queue_url, array $parameters)
  423. {
  424. $parameters['AWSAccessKeyId'] = $this->_getAccessKey();
  425. $parameters['SignatureVersion'] = $this->_sqsSignatureVersion;
  426. $parameters['Timestamp'] = gmdate('Y-m-d\TH:i:s\Z', time()+10);
  427. $parameters['Version'] = $this->_sqsApiVersion;
  428. $parameters['SignatureMethod'] = $this->_sqsSignatureMethod;
  429. $parameters['Signature'] = $this->_signParameters($queue_url, $parameters);
  430. return $parameters;
  431. }
  432. /**
  433. * Computes the RFC 2104-compliant HMAC signature for request parameters
  434. *
  435. * This implements the Amazon Web Services signature, as per the following
  436. * specification:
  437. *
  438. * 1. Sort all request parameters (including <tt>SignatureVersion</tt> and
  439. * excluding <tt>Signature</tt>, the value of which is being created),
  440. * ignoring case.
  441. *
  442. * 2. Iterate over the sorted list and append the parameter name (in its
  443. * original case) and then its value. Do not URL-encode the parameter
  444. * values before constructing this string. Do not use any separator
  445. * characters when appending strings.
  446. *
  447. * @param string $queue_url Queue URL
  448. * @param array $parameters the parameters for which to get the signature.
  449. *
  450. * @return string the signed data.
  451. */
  452. protected function _signParameters($queue_url, array $paramaters)
  453. {
  454. $data = "GET\n";
  455. $data .= $this->_sqsEndpoint . "\n";
  456. if ($queue_url !== null) {
  457. $data .= parse_url($queue_url, PHP_URL_PATH);
  458. }
  459. else {
  460. $data .= '/';
  461. }
  462. $data .= "\n";
  463. uksort($paramaters, 'strcmp');
  464. unset($paramaters['Signature']);
  465. $arrData = array();
  466. foreach($paramaters as $key => $value) {
  467. $arrData[] = $key . '=' . str_replace('%7E', '~', urlencode($value));
  468. }
  469. $data .= implode('&', $arrData);
  470. $hmac = Zend_Crypt_Hmac::compute($this->_getSecretKey(), 'SHA256', $data, Zend_Crypt_Hmac::BINARY);
  471. return base64_encode($hmac);
  472. }
  473. }