/plugins/backupbuddy2/destinations/_s3lib/aws-sdk/extensions/dynamodbsessionhandler.class.php
PHP | 518 lines | 252 code | 63 blank | 203 comment | 17 complexity | c4b8284459c1dedc345a9f5fc23b5bd4 MD5 | raw file
- <?php
- /*
- * Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
- /**
- * Provides an interface for using Amazon DynamoDB as a session store by hooking into
- * PHP's session handler hooks. This class is not auto-loaded, and must be included
- * manually or via the <code>AmazonDynamoDB::register_session_hander()</code> method.
- *
- * Once registered, You may use the native <code>$_SESSION</code> superglobal and
- * session functions, and the sessions will be stored automatically in DynamoDB.
- */
- class DynamoDBSessionHandler
- {
- /**
- * @var AmazonDynamoDB The DyanamoDB client.
- */
- protected $_dynamodb = null;
- /**
- * @var string The session save path (see <php:session_save_path()>).
- */
- protected $_save_path = null;
- /**
- * @var string The session name (see <php:session_name()>).
- */
- protected $_session_name = null;
- /**
- * @var boolean Keeps track of if the session is open.
- */
- protected $_open_session = null;
- /**
- * @var boolean Keeps track of whether the session is open.
- */
- protected $_session_written = false;
- /**
- * @var string The name of the DynamoDB table in which to store sessions.
- */
- protected $_table_name = 'sessions';
- /**
- * @var string The name of the hash key in the DynamoDB sessions table.
- */
- protected $_hash_key = 'id';
- /**
- * @var integer The lifetime of an inactive session before it should be garbage collected.
- */
- protected $_session_lifetime = 0;
- /**
- * @var boolean Whether or not the session handler should do consistent reads from DynamoDB.
- */
- protected $_consistent_reads = true;
- /**
- * @var boolean Whether or not the session handler should do session locking.
- */
- protected $_session_locking = true;
- /**
- * @var integer Maximum time, in seconds, that the session handler should take to acquire a lock.
- */
- protected $_max_lock_wait_time = 30;
- /**
- * @var integer Minimum time, in microseconds, that the session handler should wait to retry acquiring a lock.
- */
- protected $_min_lock_retry_utime = 10000;
- /**
- * @var integer Maximum time, in microseconds, that the session handler should wait to retry acquiring a lock.
- */
- protected $_max_lock_retry_utime = 100000;
- /**
- * @var array Type for casting the configuration options.
- */
- protected static $_option_types = array(
- 'table_name' => 'string',
- 'hash_key' => 'string',
- 'session_lifetime' => 'integer',
- 'consistent_reads' => 'boolean',
- 'session_locking' => 'boolean',
- 'max_lock_wait_time' => 'integer',
- 'min_lock_retry_utime' => 'integer',
- 'max_lock_retry_utime' => 'integer',
- );
- /**
- * Initializes the session handler and prepares the configuration options.
- *
- * @param AmazonDynamoDB $dynamodb (Required) An instance of the DynamoDB client.
- * @param array $options (Optional) Configuration options.
- */
- public function __construct(AmazonDynamoDB $dynamodb, array $options = array())
- {
- // Store the AmazonDynamoDB client for use in the session handler
- $this->_dynamodb = $dynamodb;
- // Do type conversions on options and store the values
- foreach ($options as $key => $value)
- {
- if (isset(self::$_option_types[$key]))
- {
- settype($value, self::$_option_types[$key]);
- $this->{'_' . $key} = $value;
- }
- }
- // Make sure the lifetime is positive. Use the gc_maxlifetime otherwise
- if ($this->_session_lifetime <= 0)
- {
- $this->_session_lifetime = (integer) ini_get('session.gc_maxlifetime');
- }
- }
- /**
- * Destruct the session handler and make sure the session gets written.
- *
- * NOTE: It is usually better practice to call <code>session_write_close()</code>
- * manually in your application as soon as session modifications are complete. This
- * is especially true if session locking is enabled (which it is by default).
- *
- * @see http://php.net/manual/en/function.session-set-save-handler.php#refsect1-function.session-set-save-handler-notes
- */
- public function __destruct()
- {
- session_write_close();
- }
- /**
- * Register DynamoDB as a session handler.
- *
- * Uses the PHP-provided method to register this class as a session handler.
- *
- * @return DynamoDBSessionHandler Chainable.
- */
- public function register()
- {
- session_set_save_handler(
- array($this, 'open'),
- array($this, 'close'),
- array($this, 'read'),
- array($this, 'write'),
- array($this, 'destroy'),
- array($this, 'garbage_collect')
- );
- return $this;
- }
- /**
- * Checks if the session is open and writable.
- *
- * @return boolean Whether or not the session is still open for writing.
- */
- public function is_session_open()
- {
- return (boolean) $this->_open_session;
- }
- /**
- * Delegates to <code>session_start()</code>
- *
- * @return DynamoDBSessionHandler Chainable.
- */
- public function open_session()
- {
- session_start();
- return $this;
- }
- /**
- * Delegates to <code>session_commit()</code>
- *
- * @return DynamoDBSessionHandler Chainable.
- */
- public function close_session()
- {
- session_commit();
- return $this;
- }
- /**
- * Delegates to <code>session_destroy()</code>
- *
- * @return DynamoDBSessionHandler Chainable.
- */
- public function destroy_session()
- {
- session_destroy();
- return $this;
- }
- /**
- * Open a session for writing. Triggered by <php:session_start()>.
- *
- * Part of the standard PHP session handler interface.
- *
- * @param string $save_path (Required) The session save path (see <php:session_save_path()>).
- * @param string $session_name (Required) The session name (see <php:session_name()>).
- * @return boolean Whether or not the operation succeeded.
- */
- public function open($save_path, $session_name)
- {
- $this->_save_path = $save_path;
- $this->_session_name = $session_name;
- $this->_open_session = session_id();
- return true;
- }
- /**
- * Close a session from writing
- *
- * Part of the standard PHP session handler interface
- *
- * @return boolean Success
- */
- public function close()
- {
- if (!$this->_session_written)
- {
- // Ensure that the session is unlocked even if the write did not happen
- $id = $this->_open_session;
- $response = $this->_dynamodb->update_item(array(
- 'TableName' => $this->_table_name,
- 'Key' => array('HashKeyElement' => $this->_dynamodb->attribute($this->_id($id))),
- 'AttributeUpdates' => array(
- 'expires' => $this->_dynamodb->attribute(time() + $this->_session_lifetime, 'update'),
- 'lock' => array('Action' => AmazonDynamoDB::ACTION_DELETE)
- ),
- ));
- $this->_session_written = $response->isOK();
- }
- $this->_open_session = null;
- return $this->_session_written;
- }
- /**
- * Read a session stored in DynamoDB
- *
- * Part of the standard PHP session handler interface
- *
- * @param string $id (Required) The session ID.
- * @return string The session data.
- */
- public function read($id)
- {
- $result = '';
- // Get the session data from DynamoDB (acquire a lock if locking is enabled)
- if ($this->_session_locking)
- {
- $response = $this->_lock_and_read($id);
- $node_name = 'Attributes';
- }
- else
- {
- $response = $this->_dynamodb->get_item(array(
- 'TableName' => $this->_table_name,
- 'Key' => array('HashKeyElement' => $this->_dynamodb->attribute($this->_id($id))),
- 'ConsistentRead' => $this->_consistent_reads,
- ));
- $node_name = 'Item';
- }
- if ($response && $response->isOK())
- {
- $item = array();
- // Get the data from the DynamoDB response
- if ($response->body->{$node_name})
- {
- foreach ($response->body->{$node_name}->children() as $key => $value)
- {
- $type = $value->children()->getName();
- $item[$key] = $value->{$type}->to_string();
- }
- }
- if (isset($item['expires']) && isset($item['data']))
- {
- // Check the expiration date before using
- if ($item['expires'] > time())
- {
- $result = $item['data'];
- }
- else
- {
- $this->destroy($id);
- }
- }
- }
- return $result;
- }
- /**
- * Write a session to DynamoDB.
- *
- * Part of the standard PHP session handler interface.
- *
- * @param string $id (Required) The session ID.
- * @param string $data (Required) The session data.
- * @return boolean Whether or not the operation succeeded.
- */
- public function write($id, $data)
- {
- // Write the session data to DynamoDB
- $response = $this->_dynamodb->put_item(array(
- 'TableName' => $this->_table_name,
- 'Item' => $this->_dynamodb->attributes(array(
- $this->_hash_key => $this->_id($id),
- 'expires' => time() + $this->_session_lifetime,
- 'data' => $data,
- )),
- ));
- $this->_session_written = $response->isOK();
- return $this->_session_written;
- }
- /**
- * Delete a session stored in DynamoDB.
- *
- * Part of the standard PHP session handler interface.
- *
- * @param string $id (Required) The session ID.
- * @param boolean $garbage_collect_mode (Optional) Whether or not the handler is doing garbage collection.
- * @return boolean Whether or not the operation succeeded.
- */
- public function destroy($id, $garbage_collect_mode = false)
- {
- // Make sure we don't prefix the ID a second time
- if (!$garbage_collect_mode)
- {
- $id = $this->_id($id);
- }
- $delete_options = array(
- 'TableName' => $this->_table_name,
- 'Key' => array('HashKeyElement' => $this->_dynamodb->attribute($id)),
- );
- // Make sure not to garbage collect locked sessions
- if ($garbage_collect_mode && $this->_session_locking)
- {
- $delete_options['Expected'] = array('lock' => array('Exists' => false));
- }
- // Send the delete request to DynamoDB
- $response = $this->_dynamodb->delete_item($delete_options);
- $this->_session_written = $response->isOK();
- return $this->_session_written;
- }
- /**
- * Performs garbage collection on the sessions stored in the DynamoDB table.
- *
- * Part of the standard PHP session handler interface.
- *
- * @param integer $maxlifetime (Required) The value of <code>session.gc_maxlifetime</code>. Ignored.
- * @return boolean Whether or not the operation succeeded.
- */
- public function garbage_collect($maxlifetime = null)
- {
- // Send a search request to DynamoDB looking for expired sessions
- $response = $this->_dynamodb->scan(array(
- 'TableName' => $this->_table_name,
- 'ScanFilter' => array('expires' => array(
- 'AttributeValueList' => array($this->_dynamodb->attribute(time())),
- 'ComparisonOperator' => AmazonDynamoDB::CONDITION_LESS_THAN,
- )),
- ));
- // Delete the expired sessions
- if ($response->isOK())
- {
- $deleted = array();
- // Get the ID of and delete each session that is expired
- foreach ($response->body->Items as $item)
- {
- $id = (string) $item->{$this->_hash_key}->{AmazonDynamoDB::TYPE_STRING};
- $deleted[$id] = $this->destroy($id, true);
- }
- // Return true if all of the expired sessions were deleted
- return (array_sum($deleted) === count($deleted));
- }
- return false;
- }
- /**
- * Creates a table in DynamoDB for session storage according to provided configuration options.
- *
- * Note: Table creation may also be done via the AWS Console, which might make the most sense for this use case.
- *
- * @param integer $read_capacity_units (Required) Read capacity units for table throughput.
- * @param integer $write_capacity_units (Required) Write capacity units for table throughput.
- * @return boolean Returns <code>true</code> on success.
- */
- public function create_sessions_table($read_capacity_units = 10, $write_capacity_units = 5)
- {
- $response = $this->_dynamodb->create_table(array(
- 'TableName' => $this->_table_name,
- 'KeySchema' => array(
- 'HashKeyElement' => array(
- 'AttributeName' => $this->_hash_key,
- 'AttributeType' => AmazonDynamoDB::TYPE_STRING,
- )
- ),
- 'ProvisionedThroughput' => array(
- 'ReadCapacityUnits' => $read_capacity_units,
- 'WriteCapacityUnits' => $write_capacity_units,
- )
- ));
- return $response->isOK();
- }
- /**
- * Deletes the session storage table from DynamoDB.
- *
- * Note: Table deletion may also be done via the AWS Console, which might make the most sense for this use case.
- *
- * @return boolean Returns <code>true</code> on success.
- */
- public function delete_sessions_table()
- {
- $response = $this->_dynamodb->delete_table(array('TableName' => $this->_table_name));
- return $response->isOK();
- }
- /**
- * Prefix the session ID with the session name and prepare for DynamoDB usage
- *
- * @param string $id (Required) The session ID.
- * @return array The HashKeyElement value formatted as an array.
- */
- protected function _id($id)
- {
- return trim($this->_session_name . '_' . $id, '_');
- }
- /**
- * Acquires a lock on a session in DynamoDB using conditional updates.
- *
- * WARNING: There is a <code>while(true);</code> in here.
- *
- * @param string $id (Required) The session ID.
- * @return CFResponse The response from DynamoDB.
- */
- protected function _lock_and_read($id)
- {
- $now = time();
- $timeout = $now + $this->_max_lock_wait_time;
- do
- {
- // Acquire the lock
- $response = $this->_dynamodb->update_item(array(
- 'TableName' => $this->_table_name,
- 'Key' => array('HashKeyElement' => $this->_dynamodb->attribute($this->_id($id))),
- 'AttributeUpdates' => array('lock' => $this->_dynamodb->attribute(1, 'update')),
- 'Expected' => array('lock' => array('Exists' => false)),
- 'ReturnValues' => 'ALL_NEW',
- ));
- // If lock succeeds (or times out), exit the loop, otherwise sleep and try again
- if ($response->isOK() || $now >= $timeout)
- {
- return $response;
- }
- elseif (stripos((string) $response->body->asXML(), 'ConditionalCheckFailedException') !== false)
- {
- usleep(rand($this->_min_lock_retry_utime, $this->_max_lock_retry_utime));
- $now = time();
- }
- else
- {
- return null;
- }
- }
- while (true);
- }
- }