/*
* Copyright 2012-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.
*/
using System;
using System.IO;
using System.Web;
using System.Web.Configuration;
using System.Configuration;
using System.Configuration.Provider;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web.SessionState;
using System.Text;
using System.Threading;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.Runtime;
using Amazon.Runtime.Internal.Util;
using Amazon.Util;
namespace Amazon.SessionProvider
{
///
/// DynamoDBSessionStateStore is a custom session state provider that can be used inside of an ASP.NET application. Session state is saved
/// inside a DynamoDB table that can be configured in the web.config. If the table does not exist the provider will create
/// it during initialization with default read and write units set to 10 and 5 unless configured otherwise. If the table is created
/// the application startup will block for about a minute while the table is being created.
///
/// Example web.config entry setting up the session state provider.
///
/// <sessionState
/// mode="Custom"
/// customProvider="DynamoDBSessionStoreProvider">
/// <providers>
/// <add name="DynamoDBSessionStoreProvider"
/// type="Amazon.SessionProvider.DynamoDBSessionStateStore, AWS.SessionProvider"
/// AWSProfileName="default"
/// AWSProfilesLocation=".aws/credentials"
/// Region="us-east-1"
/// Table="ASP.NET_SessionState"
/// TTLAttributeName="ExpirationTime"
/// TTLExpiredSessionsSeconds="86400"
/// />
/// </providers>
/// </sessionState>
///
///
///
/// The schema for the table used to store session requires a string hash key with no range key. The provider will look up the name of the hash key during
/// initialization so any name can be given for the hash key.
///
///
///
/// Below is a list of configuration attributes that can specified in the provider element in the web.config.
///
///
/// Config Constant
/// Use
///
/// -
/// AWSProfileName
/// Profile used. This can be set at either the provider or in the appSettings.
///
/// -
/// AWSProfilesLocation
/// Location of the credentials file. This can be set at either the provider or in the appSettings.
///
/// -
/// Region
/// Required string attribute. The region to use DynamoDB in. Possible values are us-east-1, us-west-1, us-west-2, eu-west-1, ap-northeast-1, ap-southeast-1.
///
/// -
/// Service URL
/// The URL of the DynamoDB endpoint. This can be used instead of region. This property is commonly used for connecting to DynamoDB Local (e.g. http://localhost:8000/)
///
/// -
/// Application
/// Optional string attribute. Application is used to partition the session data in the table so it can be used for more than one application.
///
/// -
/// Table
/// Optional string attribute. The table used to store session data. The default is ASP.NET_SessionState.
///
/// -
/// ReadCapacityUnits
/// Optional int attribute. The read capacity units if the table is created. The default is 10.
///
/// -
/// WriteCapacityUnits
/// Optional int attribute. The write capacity units if the table is created. The default is 5.
///
/// -
/// UseOnDemandReadWriteCapacity
/// Optional boolean attribute. UseOnDemandReadWriteCapacity controls whether the table will be created with its read/write capacity set to On-Demand. Default is false.
///
/// -
/// CreateIfNotExist
/// Optional boolean attribute. CreateIfNotExist controls whether the table will be auto created if it doesn't exist. Default is true.
///
/// -
/// StrictDisableSession
/// Optional boolean attribute. If EnabledSessionState is False globally or on an individual page/view/controller, ASP.NET will still send a keepalive request to dynamo. Setting this to true disables keepalive requests when EnableSessionState is False. Default is false.
///
/// -
/// TTLAttributeName
/// Optional string attribute. The name of the TTL attribute for the table. This must be specified for session items to contain TTL-compatible data.
///
/// -
/// TTLExpiredSessionsSeconds
/// Optional int attribute. The minimum number of seconds after session expiration before sessions are eligible for TTL. By default this is 0. This value must be non-negative.
///
///
///
///
public class DynamoDBSessionStateStore : SessionStateStoreProviderBase
{
private const string CURRENT_RECORD_FORMAT_VERSION = "1";
private const int DESCRIBE_INTERVAL = 5000;
private const string ACTIVE_STATUS = "Active";
private static readonly GetItemOperationConfig CONSISTENT_READ_GET = new GetItemOperationConfig();
private static readonly UpdateItemOperationConfig LOCK_UPDATE_CONFIG = new UpdateItemOperationConfig();
private static readonly ILogger _logger = Logger.GetLogger(typeof(DynamoDBSessionStateStore));
static DynamoDBSessionStateStore()
{
CONSISTENT_READ_GET.ConsistentRead = true;
LOCK_UPDATE_CONFIG.Expected = new Document();
LOCK_UPDATE_CONFIG.Expected[ATTRIBUTE_LOCKED] = false;
LOCK_UPDATE_CONFIG.ReturnValues = ReturnValues.AllNewAttributes;
}
// Possible config names set in the web.config.
public const string CONFIG_ACCESSKEY = "AWSAccessKey";
public const string CONFIG_SECRETKEY = "AWSSecretKey";
public const string CONFIG_PROFILENAME = "AWSProfileName";
public const string CONFIG_PROFILESLOCATION = "AWSProfilesLocation";
public const string CONFIG_APPLICATION = "Application";
public const string CONFIG_TABLE = "Table";
public const string CONFIG_REGION = "Region";
public const string CONFIG_SERVICE_URL = "ServiceURL";
public const string CONFIG_INITIAL_READ_UNITS = "ReadCapacityUnits";
public const string CONFIG_INITIAL_WRITE_UNITS = "WriteCapacityUnits";
public const string CONFIG_ON_DEMAND_READ_WRITE_CAPACITY = "UseOnDemandReadWriteCapacity";
public const string CONFIG_CREATE_TABLE_IF_NOT_EXIST = "CreateIfNotExist";
public const string CONFIG_STRICT_DISABLE_SESSION = "StrictDisableSession";
public const string CONFIG_TTL_ATTRIBUTE = "TTLAttributeName";
public const string CONFIG_TTL_EXPIRED_SESSIONS_SECONDS = "TTLExpiredSessionsSeconds";
// This is not const because we will use whatever is the hash key defined for
// the table as long as it is a string.
private static string ATTRIBUTE_SESSION_ID = "SessionId";
// The attribute names stored for the session record.
public const string ATTRIBUTE_CREATE_DATE = "CreateDate";
public const string ATTRIBUTE_LOCKED = "Locked";
public const string ATTRIBUTE_LOCK_DATE = "LockDate";
public const string ATTRIBUTE_LOCK_ID = "LockId";
public const string ATTRIBUTE_EXPIRES = "Expires";
public const string ATTRIBUTE_SESSION_ITEMS = "SessionItems";
public const string ATTRIBUTE_FLAGS = "Flags";
public const string ATTRIBUTE_RECORD_FORMAT_VERSION = "Ver";
const string DEFAULT_TABLENAME = "ASP.NET_SessionState";
// Fields that come from the web.config
string _accessKey;
string _secretKey;
string _profileName;
string _profilesLocation;
string _tableName = DEFAULT_TABLENAME;
string _regionName;
string _serviceURL;
string _application = "";
int _initialReadUnits = 10;
int _initialWriteUnits = 5;
bool _useOnDemandReadWriteCapacity = false;
bool _createIfNotExist = true;
bool _strictDisableSession = false;
uint _ttlExtraSeconds = 0;
string _ttlAttributeName = null;
IAmazonDynamoDB _ddbClient;
Table _table;
TimeSpan _timeout = new TimeSpan(0, 20, 0);
///
/// Default Constructor
///
public DynamoDBSessionStateStore()
{
}
///
/// Constructor for testing.
///
///
public DynamoDBSessionStateStore(IAmazonDynamoDB ddbClient)
{
this._ddbClient = ddbClient;
SetupTable();
}
///
/// Constructor for testing.
///
///
///
public DynamoDBSessionStateStore(string name, NameValueCollection config)
{
Initialize(name, config);
}
///
/// Gets the name of the table used to store session data.
///
public string TableName
{
get { return this._tableName; }
}
///
/// Initializes the provider by pulling the config info from the web.config and validate/create the DynamoDB table.
/// If the table is being created this method will block until the table is active.
///
///
///
public override void Initialize(string name, NameValueCollection config)
{
_logger.InfoFormat("Initialize : Initializing Session provider {0}", name);
if (config == null)
throw new ArgumentNullException("config");
base.Initialize(name, config);
GetConfigSettings(config);
RegionEndpoint region = null;
if(!string.IsNullOrEmpty(this._regionName))
region = RegionEndpoint.GetBySystemName(this._regionName);
AWSCredentials credentials = null;
if (!string.IsNullOrEmpty(this._accessKey))
{
credentials = new BasicAWSCredentials(this._accessKey, this._secretKey);
}
else if (!string.IsNullOrEmpty(this._profileName))
{
if (string.IsNullOrEmpty(this._profilesLocation))
credentials = new StoredProfileAWSCredentials(this._profileName);
else
credentials = new StoredProfileAWSCredentials(this._profileName, this._profilesLocation);
}
AmazonDynamoDBConfig ddbConfig = new AmazonDynamoDBConfig();
if (region != null)
ddbConfig.RegionEndpoint = region;
if (!string.IsNullOrEmpty(this._serviceURL))
ddbConfig.ServiceURL = this._serviceURL;
if (credentials != null)
{
this._ddbClient = new AmazonDynamoDBClient(credentials, ddbConfig);
}
else
{
this._ddbClient = new AmazonDynamoDBClient(ddbConfig);
}
((AmazonDynamoDBClient)this._ddbClient).BeforeRequestEvent += DynamoDBSessionStateStore_BeforeRequestEvent;
SetupTable();
}
const string UserAgentHeader = "User-Agent";
void DynamoDBSessionStateStore_BeforeRequestEvent(object sender, RequestEventArgs e)
{
Amazon.Runtime.WebServiceRequestEventArgs args = e as Amazon.Runtime.WebServiceRequestEventArgs;
if (args == null || !args.Headers.ContainsKey(UserAgentHeader))
return;
args.Headers[UserAgentHeader] = args.Headers[UserAgentHeader] + " SessionStateProvider";
}
private void SetupTable()
{
try
{
var tableConfig = CreateTableConfig();
this._table = Table.LoadTable(this._ddbClient, tableConfig);
}
catch (ResourceNotFoundException) { }
if (this._table == null)
{
if (this._createIfNotExist)
this._table = CreateTable();
else
throw new AmazonDynamoDBException(string.Format("Table {0} was not found to be used to store session state and autocreate is turned off.", this._tableName));
}
else
{
ValidateTable();
}
}
private void GetConfigSettings(NameValueCollection config)
{
this._accessKey = config[CONFIG_ACCESSKEY];
this._secretKey = config[CONFIG_SECRETKEY];
this._profileName= config[CONFIG_PROFILENAME];
this._profilesLocation = config[CONFIG_PROFILESLOCATION];
this._regionName = config[CONFIG_REGION];
this._serviceURL = config[CONFIG_SERVICE_URL];
if (!string.IsNullOrEmpty(config[CONFIG_TABLE]))
{
this._tableName = config[CONFIG_TABLE];
}
if (!string.IsNullOrEmpty(config[CONFIG_APPLICATION]))
{
this._application = config[CONFIG_APPLICATION];
}
if (!string.IsNullOrEmpty(config[CONFIG_CREATE_TABLE_IF_NOT_EXIST]))
{
this._createIfNotExist = bool.Parse(config[CONFIG_CREATE_TABLE_IF_NOT_EXIST]);
}
if (!string.IsNullOrEmpty(config[CONFIG_INITIAL_READ_UNITS]))
{
this._initialReadUnits = int.Parse(config[CONFIG_INITIAL_READ_UNITS]);
}
if (!string.IsNullOrEmpty(config[CONFIG_INITIAL_WRITE_UNITS]))
{
this._initialWriteUnits = int.Parse(config[CONFIG_INITIAL_WRITE_UNITS]);
}
if (!string.IsNullOrEmpty(config[CONFIG_ON_DEMAND_READ_WRITE_CAPACITY]))
{
this._useOnDemandReadWriteCapacity = bool.Parse(config[CONFIG_ON_DEMAND_READ_WRITE_CAPACITY]);
}
if (!string.IsNullOrEmpty(config[CONFIG_STRICT_DISABLE_SESSION]))
{
this._strictDisableSession = bool.Parse(config[CONFIG_STRICT_DISABLE_SESSION]);
}
if (!string.IsNullOrEmpty(config[CONFIG_TTL_ATTRIBUTE]))
{
this._ttlAttributeName = config[CONFIG_TTL_ATTRIBUTE];
}
if (!string.IsNullOrEmpty(config[CONFIG_TTL_EXPIRED_SESSIONS_SECONDS]))
{
this._ttlExtraSeconds = uint.Parse(config[CONFIG_TTL_EXPIRED_SESSIONS_SECONDS]);
}
string applicationName = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath;
if (applicationName != null)
{
Configuration cfg = WebConfigurationManager.OpenWebConfiguration(applicationName);
if (cfg != null)
{
SessionStateSection sessionConfig = cfg.GetSection("system.web/sessionState") as SessionStateSection;
if (sessionConfig != null)
{
this._timeout = sessionConfig.Timeout;
}
}
}
}
///
/// Provider returns false for this method.
///
///
///
public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
{
return false;
}
///
/// Returns read-only session-state data from the DynamoDB table.
///
///
///
///
///
///
///
///
public override SessionStateStoreData GetItem(HttpContext context,
string sessionId,
out bool locked,
out TimeSpan lockAge,
out object lockId,
out SessionStateActions actionFlags)
{
LogInfo("GetItem", sessionId, context);
return GetSessionStoreItem(false, context, sessionId, out locked,
out lockAge, out lockId, out actionFlags);
}
///
/// Returns session-state data from the DynamoDB table.
///
///
///
///
///
///
///
///
public override SessionStateStoreData GetItemExclusive(HttpContext context,
string sessionId,
out bool locked,
out TimeSpan lockAge,
out object lockId,
out SessionStateActions actionFlags)
{
LogInfo("GetItemExclusive", sessionId, context);
return GetSessionStoreItem(true, context, sessionId, out locked,
out lockAge, out lockId, out actionFlags);
}
///
/// Get the session for DynamoDB and optionally lock the record.
///
///
///
///
///
///
///
///
///
private SessionStateStoreData GetSessionStoreItem(bool lockRecord,
HttpContext context,
string sessionId,
out bool locked,
out TimeSpan lockAge,
out object lockId,
out SessionStateActions actionFlags)
{
LogInfo("GetSessionStoreItem", sessionId, lockRecord, context);
// Initial values for return value and out parameters.
SessionStateStoreData item = null;
lockAge = TimeSpan.Zero;
lockId = Guid.NewGuid().ToString();
locked = false;
actionFlags = SessionStateActions.None;
bool foundRecord = false;
bool deleteData = false;
DateTime newLockedDate = DateTime.Now;
Document session = null;
if (lockRecord)
{
Document lockDoc = new Document();
lockDoc[ATTRIBUTE_SESSION_ID] = GetHashKey(sessionId);
lockDoc[ATTRIBUTE_LOCK_ID] = lockId.ToString();
lockDoc[ATTRIBUTE_LOCKED] = true;
lockDoc[ATTRIBUTE_LOCK_DATE] = DateTime.Now;
try
{
session = this._table.UpdateItem(lockDoc, LOCK_UPDATE_CONFIG);
locked = false;
}
catch (ConditionalCheckFailedException)
{
// This means the record is already locked by another request.
locked = true;
}
}
if (session == null)
{
session = this._table.GetItem(GetHashKey(sessionId), CONSISTENT_READ_GET);
if (session == null && lockRecord)
{
locked = true;
}
}
string serializedItems = null;
if (session != null)
{
DateTime expire = (DateTime)session[ATTRIBUTE_EXPIRES];
if (expire < DateTime.Now)
{
deleteData = true;
locked = false;
}
else
{
foundRecord = true;
DynamoDBEntry entry;
if (session.TryGetValue(ATTRIBUTE_SESSION_ITEMS, out entry))
{
serializedItems = (string)entry;
}
if (session.Contains(ATTRIBUTE_LOCK_ID))
lockId = (string)session[ATTRIBUTE_LOCK_ID];
if (session.Contains(ATTRIBUTE_FLAGS))
actionFlags = (SessionStateActions)((int)session[ATTRIBUTE_FLAGS]);
if (session.Contains(ATTRIBUTE_LOCK_DATE) && session[ATTRIBUTE_LOCK_DATE] != null)
{
DateTime lockDate = (DateTime)session[ATTRIBUTE_LOCK_DATE];
lockAge = DateTime.Now.Subtract(lockDate);
}
}
}
if (deleteData)
{
this.deleteItem(sessionId);
}
// The record was not found. Ensure that locked is false.
if (!foundRecord)
{
locked = false;
lockId = null;
}
// If the record was found and you obtained a lock, then clear the actionFlags,
// and create the SessionStateStoreItem to return.
if (foundRecord && !locked)
{
if (actionFlags == SessionStateActions.InitializeItem)
{
Document updateDoc = new Document();
updateDoc[ATTRIBUTE_SESSION_ID] = GetHashKey(sessionId);
updateDoc[ATTRIBUTE_FLAGS] = 0;
this._table.UpdateItem(updateDoc);
item = CreateNewStoreData(context, (int)this._timeout.TotalMinutes);
}
else
{
item = deserialize(context, serializedItems, (int)this._timeout.TotalMinutes);
}
}
return item;
}
///
/// Updates the session-item information in the session-state data store with values from the current request, and clears the lock on the data.
///
/// The HttpContext for the current request.
/// The session identifier for the current request.
/// The SessionStateStoreData object that contains the current session values to be stored.
/// The lock identifier for the current request.
/// true to identify the session item as a new item; false to identify the session item as an existing item.
public override void SetAndReleaseItemExclusive(HttpContext context,
string sessionId,
SessionStateStoreData item,
object lockId,
bool newItem)
{
LogInfo("SetAndReleaseItemExclusive", sessionId, lockId, newItem, context);
string serialized = serialize(item.Items as SessionStateItemCollection);
var expiration = DateTime.Now.Add(this._timeout);
Document newValues = new Document();
newValues[ATTRIBUTE_SESSION_ID] = GetHashKey(sessionId);
newValues[ATTRIBUTE_LOCKED] = false;
newValues[ATTRIBUTE_LOCK_ID] = null;
newValues[ATTRIBUTE_LOCK_DATE] = DateTime.Now;
newValues[ATTRIBUTE_EXPIRES] = expiration;
newValues[ATTRIBUTE_FLAGS] = 0;
newValues[ATTRIBUTE_SESSION_ITEMS] = serialized;
newValues[ATTRIBUTE_RECORD_FORMAT_VERSION] = CURRENT_RECORD_FORMAT_VERSION;
SetTTLAttribute(newValues, expiration);
if (newItem)
{
newValues[ATTRIBUTE_CREATE_DATE] = DateTime.Now;
this._table.PutItem(newValues);
}
else
{
Document expected = new Document();
expected[ATTRIBUTE_LOCK_ID] = lockId.ToString();
// Not really any reason the condition should fail unless we get in some sort of weird
// app pool reset mode.
try
{
this._table.UpdateItem(newValues, new UpdateItemOperationConfig() { Expected = expected });
}
catch (ConditionalCheckFailedException) { LogInfo("(SetAndReleaseItemExclusive) Conditional check failed for update.", sessionId, context); }
}
}
///
/// Releases a lock on an item in the session data store.
///
/// The HttpContext for the current request.
/// The session identifier for the current request.
/// The lock identifier for the current request.
public override void ReleaseItemExclusive(HttpContext context, string sessionId, object lockId)
{
LogInfo("ReleaseItemExclusive", sessionId, lockId, context);
Document doc = this._table.GetItem(GetHashKey(sessionId), CONSISTENT_READ_GET);
if (doc == null)
{
LogError("ReleaseItemExclusive Failed to retrieve state for session id: " + sessionId, sessionId, lockId, context);
return;
}
var expiration = DateTime.Now.Add(this._timeout);
doc[ATTRIBUTE_LOCKED] = false;
doc[ATTRIBUTE_EXPIRES] = expiration;
SetTTLAttribute(doc, expiration);
Document expected = new Document();
expected[ATTRIBUTE_LOCK_ID] = lockId.ToString();
try
{
this._table.UpdateItem(doc, new UpdateItemOperationConfig() { Expected = expected });
}
catch (ConditionalCheckFailedException) { LogInfo("(ReleaseItemExclusive) Conditional check failed for update.", sessionId, context); }
}
///
/// Removes the session record for DynamoDB.
///
///
///
///
///
public override void RemoveItem(HttpContext context, string sessionId, object lockId, SessionStateStoreData item)
{
LogInfo("RemoveItem", sessionId, lockId, context);
if (lockId == null)
{
deleteItem(sessionId);
}
else
{
Document doc = this._table.GetItem(GetHashKey(sessionId), CONSISTENT_READ_GET);
if (doc.Contains(ATTRIBUTE_LOCK_ID))
{
string currentLockId = (string)doc[ATTRIBUTE_LOCK_ID];
if (string.Equals(currentLockId, lockId))
{
deleteItem(sessionId);
}
}
}
}
///
/// Creates an initial session record in the DynamoDB table.
///
///
///
///
public override void CreateUninitializedItem(HttpContext context, string sessionId, int timeout)
{
LogInfo("CreateUninitializedItem", sessionId, timeout, context);
var expiration = DateTime.Now.Add(this._timeout);
Document session = new Document();
session[ATTRIBUTE_SESSION_ID] = GetHashKey(sessionId);
session[ATTRIBUTE_LOCKED] = false;
session[ATTRIBUTE_CREATE_DATE] = DateTime.Now;
session[ATTRIBUTE_EXPIRES] = expiration;
session[ATTRIBUTE_FLAGS] = 1;
session[ATTRIBUTE_RECORD_FORMAT_VERSION] = CURRENT_RECORD_FORMAT_VERSION;
SetTTLAttribute(session, expiration);
this._table.PutItem(session);
}
///
/// Creates a new SessionStateStoreData object to be used for the current request.
///
///
///
///
public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
{
LogInfo("CreateNewStoreData", timeout, context);
HttpStaticObjectsCollection sessionStatics = null;
if (context != null)
sessionStatics = SessionStateUtility.GetSessionStaticObjects(context);
return new SessionStateStoreData(new SessionStateItemCollection(), sessionStatics, timeout);
}
///
/// Updates the expiration date and time of an item in the DynamoDB table.
///
///
///
public override void ResetItemTimeout(HttpContext context, string sessionId)
{
LogInfo("ResetItemTimeout", sessionId, context);
var suppressKeepalive = _strictDisableSession && context.Session == null;
if (suppressKeepalive)
return;
var expiration = DateTime.Now.Add(this._timeout);
Document doc = new Document();
doc[ATTRIBUTE_SESSION_ID] = GetHashKey(sessionId);
doc[ATTRIBUTE_LOCKED] = false;
doc[ATTRIBUTE_EXPIRES] = expiration;
SetTTLAttribute(doc, expiration);
this._table.UpdateItem(doc);
}
///
/// A utility method for cleaning up expired sessions that IIS failed to delete. The method performs a scan on the ASP.NET_SessionState table
/// with a condition that the expiration date is in the past and calls delete on all the keys returned. Scans can be costly on performance
/// so use this method sparingly like a nightly or weekly clean job.
///
/// The AmazonDynamoDB client used to find a delete expired sessions.
public static void DeleteExpiredSessions(IAmazonDynamoDB dbClient)
{
LogInfo("DeleteExpiredSessions");
DeleteExpiredSessions(dbClient, DEFAULT_TABLENAME);
}
///
/// A utility method for cleaning up expired sessions that IIS failed to delete. The method performs a scan on the table
/// with a condition that the expiration date is in the past and calls delete on all the keys returned. Scans can be costly on performance
/// so use this method sparingly like a nightly or weekly clean job.
///
/// The AmazonDynamoDB client used to find a delete expired sessions.
/// The table to search.
public static void DeleteExpiredSessions(IAmazonDynamoDB dbClient, string tableName)
{
LogInfo("DeleteExpiredSessions");
var tableConfig = CreateTableConfig(tableName);
Table table = Table.LoadTable(dbClient, tableConfig);
ScanFilter filter = new ScanFilter();
filter.AddCondition(ATTRIBUTE_EXPIRES, ScanOperator.LessThan, DateTime.Now);
ScanOperationConfig config = new ScanOperationConfig();
config.AttributesToGet = new List { ATTRIBUTE_SESSION_ID };
config.Select = SelectValues.SpecificAttributes;
config.Filter = filter;
Search search = table.Scan(config);
do
{
DocumentBatchWrite batchWrite = table.CreateBatchWrite();
List page = search.GetNextSet();
foreach (var document in page)
{
batchWrite.AddItemToDelete(document);
}
batchWrite.Execute();
} while (!search.IsDone);
}
///
/// Empty implementation of the override.
///
public override void Dispose()
{
}
///
/// Empty implementation of the override.
///
///
public override void InitializeRequest(HttpContext context)
{
}
///
/// Empty implementation of the override.
///
///
public override void EndRequest(HttpContext context)
{
}
private Table CreateTable()
{
CreateTableRequest createRequest = new CreateTableRequest
{
TableName = this._tableName,
KeySchema = new List
{
new KeySchemaElement
{
AttributeName = ATTRIBUTE_SESSION_ID, KeyType = "HASH"
}
},
AttributeDefinitions = new List
{
new AttributeDefinition
{
AttributeName = ATTRIBUTE_SESSION_ID, AttributeType = "S"
}
}
};
if (this._useOnDemandReadWriteCapacity)
{
createRequest.BillingMode = BillingMode.PAY_PER_REQUEST;
}
else
{
createRequest.ProvisionedThroughput = new ProvisionedThroughput
{
ReadCapacityUnits = this._initialReadUnits,
WriteCapacityUnits = this._initialWriteUnits
};
}
CreateTableResponse response = this._ddbClient.CreateTable(createRequest);
DescribeTableRequest descRequest = new DescribeTableRequest
{
TableName = this._tableName
};
// Wait till table is active
bool isActive = false;
while (!isActive)
{
Thread.Sleep(DESCRIBE_INTERVAL);
DescribeTableResponse descResponse = this._ddbClient.DescribeTable(descRequest);
string tableStatus = descResponse.Table.TableStatus;
if (string.Equals(tableStatus, ACTIVE_STATUS, StringComparison.InvariantCultureIgnoreCase))
isActive = true;
}
if (!string.IsNullOrEmpty(this._ttlAttributeName))
{
this._ddbClient.UpdateTimeToLive(new UpdateTimeToLiveRequest
{
TableName = this._tableName,
TimeToLiveSpecification = new TimeToLiveSpecification
{
AttributeName = this._ttlAttributeName,
Enabled = true
}
});
}
var tableConfig = CreateTableConfig();
Table table = Table.LoadTable(this._ddbClient, tableConfig);
return table;
}
private TableConfig CreateTableConfig()
{
var tableConfig = CreateTableConfig(this._tableName);
if (!string.IsNullOrEmpty(this._ttlAttributeName))
{
tableConfig.AttributesToStoreAsEpoch.Add(this._ttlAttributeName);
}
return tableConfig;
}
private static TableConfig CreateTableConfig(string tableName)
{
var tableConfig = new TableConfig(tableName)
{
Conversion = DynamoDBEntryConversion.V1
};
return tableConfig;
}
///
/// Make sure existing table is valid to be used as a session store.
///
private void ValidateTable()
{
if (this._table.HashKeys.Count != 1)
throw new AmazonDynamoDBException(string.Format("Table {0} cannot be used to store session data because it does not define a single hash key", this._tableName));
string hashKey = this._table.HashKeys[0];
KeyDescription hashKeyDescription = this._table.Keys[hashKey];
if (hashKeyDescription.Type != DynamoDBEntryType.String)
throw new AmazonDynamoDBException(string.Format("Table {0} cannot be used to store session data because hash key is not a string.", this._tableName));
if (this._table.RangeKeys.Count > 0)
throw new AmazonDynamoDBException(string.Format("Table {0} cannot be used to store session data because it contains a range key in its schema.", this._tableName));
ATTRIBUTE_SESSION_ID = hashKey;
}
private void SetTTLAttribute(Document doc, DateTime expiration)
{
if (!string.IsNullOrEmpty(this._ttlAttributeName))
doc[this._ttlAttributeName] = expiration.AddSeconds(_ttlExtraSeconds);
}
private void deleteItem(string sessionId)
{
Document doc = new Document();
doc[ATTRIBUTE_SESSION_ID] = GetHashKey(sessionId);
this._table.DeleteItem(doc);
}
private string serialize(SessionStateItemCollection items)
{
MemoryStream ms = new MemoryStream();
BinaryWriter writer = new BinaryWriter(ms);
if (items != null)
items.Serialize(writer);
writer.Close();
return Convert.ToBase64String(ms.ToArray());
}
private SessionStateStoreData deserialize(HttpContext context, string serializedItems, int timeout)
{
SessionStateItemCollection sessionItems = new SessionStateItemCollection();
if (serializedItems != null)
{
MemoryStream ms =
new MemoryStream(Convert.FromBase64String(serializedItems));
if (ms.Length > 0)
{
BinaryReader reader = new BinaryReader(ms);
sessionItems = SessionStateItemCollection.Deserialize(reader);
}
}
HttpStaticObjectsCollection statics = null;
if (context != null)
statics = SessionStateUtility.GetSessionStaticObjects(context);
return new SessionStateStoreData(sessionItems, statics, timeout);
}
///
/// Combine application and session id for hash key.
///
///
///
private string GetHashKey(string sessionId)
{
if (string.IsNullOrEmpty(this._application))
return sessionId;
return string.Format("{0}-{1}", this._application, sessionId);
}
private static void LogInfo(string methodName)
{
_logger.InfoFormat("{0}", methodName);
}
private static void LogInfo(string methodName, string sessionId, HttpContext context)
{
_logger.InfoFormat("{0} : SessionId {1}, Context {2}", methodName, sessionId ?? "NULL",
context == null ? "NULL" : "HttpContext");
}
private static void LogInfo(string methodName, string sessionId, bool lockRecord, HttpContext context)
{
_logger.InfoFormat("{0} : SessionId {1}, LockRecord {2}, Context {3} ",
methodName, sessionId ?? "NULL", lockRecord,
context == null ? "NULL" : "HttpContext");
}
private static void LogInfo(string methodName, string sessionId, object lockId, bool newItem, HttpContext context)
{
_logger.InfoFormat("{0} : SessionId {1}, LockId {2}, NewItem {3}, Context {4} ",
methodName, sessionId ?? "NULL",
lockId == null ? "NULL" : lockId.ToString(), newItem,
context == null ? "NULL" : "HttpContext");
}
private static void LogInfo(string methodName, string sessionId, object lockId, HttpContext context)
{
_logger.InfoFormat("{0} : SessionId {1}, LockId {2}, Context {3} ", methodName, sessionId ?? "NULL",
lockId == null ? "NULL" : lockId.ToString(),
context == null ? "NULL" : "HttpContext");
}
private static void LogInfo(string methodName, string sessionId, int timeout, HttpContext context)
{
_logger.InfoFormat("{0} : SessionId {1}, Timeout {2}, Context {3} ", methodName, sessionId ?? "NULL",
timeout, context == null ? "NULL" : "HttpContext");
}
private static void LogInfo(string methodName, int timeout, HttpContext context)
{
_logger.InfoFormat("{0} : Timeout {1}, Context {2} ", methodName,
timeout, context == null ? "NULL" : "HttpContext");
}
private static void LogError(string methodName, string sessionId, object lockId, HttpContext context)
{
string message = string.Format("{0} : SessionId {1}, LockId {2}, Context {3} ", methodName, sessionId ?? "NULL",
lockId == null ? "NULL" : lockId.ToString(),
context == null ? "NULL" : "HttpContext");
_logger.Error(new Exception(message), message);
}
private static void LogError(string methodName, Exception exception)
{
_logger.Error(exception, "{0} : {1}", methodName, exception.Message);
}
}
}