|
//------------------------------------------------------------------------------
// <copyright file="OutOfProcSessionStateStore.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.SessionState {
using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web;
using System.Web.Configuration;
using System.Web.Management;
using System.Web.Security.Cryptography;
using System.Web.Util;
internal sealed class OutOfProcSessionStateStore : SessionStateStoreProviderBase {
internal static readonly IntPtr INVALID_SOCKET = UnsafeNativeMethods.INVALID_HANDLE_VALUE;
internal static readonly int WHIDBEY_MAJOR_VERSION = 2;
internal const int STATE_NETWORK_TIMEOUT_DEFAULT = 10; // in sec
static string s_uribase;
static int s_networkTimeout;
#pragma warning disable 0649
static ReadWriteSpinLock s_lock;
#pragma warning restore 0649
static bool s_oneTimeInited;
static StateServerPartitionInfo s_singlePartitionInfo;
static PartitionManager s_partitionManager;
static bool s_usePartition;
static EventHandler s_onAppDomainUnload;
// We keep these info because we don't want to hold on to the config object.
static string s_configPartitionResolverType;
static string s_configStateConnectionString;
static string s_configStateConnectionStringFileName;
static int s_configStateConnectionStringLineNumber;
static bool s_configCompressionEnabled;
// Per request info
IPartitionResolver _partitionResolver;
StateServerPartitionInfo _partitionInfo;
internal override void Initialize(string name, NameValueCollection config, IPartitionResolver partitionResolver) {
_partitionResolver = partitionResolver;
Initialize(name, config);
}
public override void Initialize(string name, NameValueCollection config) {
if (String.IsNullOrEmpty(name))
name = "State Server Session State Provider";
base.Initialize(name, config);
if (!s_oneTimeInited) {
s_lock.AcquireWriterLock();
try {
if (!s_oneTimeInited) {
OneTimeInit();
}
}
finally {
s_lock.ReleaseWriterLock();
}
}
if (!s_usePartition) {
// For single partition, the connection info won't change from request to request
Debug.Assert(s_partitionManager == null);
_partitionInfo = s_singlePartitionInfo;
}
}
void OneTimeInit() {
SessionStateSection config = RuntimeConfig.GetAppConfig().SessionState;
s_configPartitionResolverType = config.PartitionResolverType;
s_configStateConnectionString = config.StateConnectionString;
s_configStateConnectionStringFileName = config.ElementInformation.Properties["stateConnectionString"].Source;
s_configStateConnectionStringLineNumber = config.ElementInformation.Properties["stateConnectionString"].LineNumber;
s_configCompressionEnabled = config.CompressionEnabled;
if (_partitionResolver == null) {
String stateConnectionString = config.StateConnectionString;
SessionStateModule.ReadConnectionString(config, ref stateConnectionString, "stateConnectionString");
s_singlePartitionInfo = (StateServerPartitionInfo)CreatePartitionInfo(stateConnectionString);
}
else {
s_usePartition = true;
s_partitionManager = new PartitionManager(new CreatePartitionInfo(CreatePartitionInfo));
}
s_networkTimeout = (int)config.StateNetworkTimeout.TotalSeconds;
string appId = HttpRuntime.AppDomainAppId;
string idHash = Convert.ToBase64String(CryptoUtil.ComputeSHA256Hash(Encoding.UTF8.GetBytes(appId)));
// Make sure that we have a absolute URI, some hosts(Cassini) don't provide this.
if (appId.StartsWith("/", StringComparison.Ordinal)) {
s_uribase = appId + "(" + idHash + ")/";
}
else {
s_uribase = "/" + appId + "(" + idHash + ")/";
}
// We only need to do this in one instance
s_onAppDomainUnload = new EventHandler(OnAppDomainUnload);
Thread.GetDomain().DomainUnload += s_onAppDomainUnload;
s_oneTimeInited = true;
}
void OnAppDomainUnload(Object unusedObject, EventArgs unusedEventArgs) {
Debug.Trace("OutOfProcSessionStateStore", "OnAppDomainUnload called");
Thread.GetDomain().DomainUnload -= s_onAppDomainUnload;
if (_partitionResolver == null) {
if (s_singlePartitionInfo != null) {
s_singlePartitionInfo.Dispose();
}
}
else {
if (s_partitionManager != null) {
s_partitionManager.Dispose();
}
}
}
internal IPartitionInfo CreatePartitionInfo(string stateConnectionString) {
string server;
bool serverIsIpv6NumericAddress;
int port;
int hr;
try {
ParseStateConnectionString(stateConnectionString, out server, out serverIsIpv6NumericAddress, out port);
// At v1, we won't accept server name that has non-ascii characters
for (int i = 0; i < server.Length; ++i) {
if (server[i] > 0x7F) {
throw new ArgumentException("stateConnectionString");
}
}
}
catch {
if (s_usePartition) {
throw new HttpException(
SR.GetString(SR.Error_parsing_state_server_partition_resolver_string, s_configPartitionResolverType));
}
else {
throw new ConfigurationErrorsException(
SR.GetString(SR.Invalid_value_for_sessionstate_stateConnectionString, s_configStateConnectionString),
s_configStateConnectionStringFileName, s_configStateConnectionStringLineNumber);
}
}
hr = UnsafeNativeMethods.SessionNDConnectToService(server);
if (hr != 0) {
throw CreateConnectionException(server, port, hr);
}
return new StateServerPartitionInfo(
new ResourcePool(new TimeSpan(0, 0, 5), int.MaxValue),
server: server,
serverIsIPv6NumericAddress: serverIsIpv6NumericAddress,
port: port);
}
private static Regex _ipv6ConnectionStringFormat = new Regex(@"^\[(?<ipv6Address>.*)\]:(?<port>\d*)$");
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = @"The exception is never bubbled up to the user.")]
internal static void ParseStateConnectionString(string stateConnectionString, out string server, out bool serverIsIPv6NumericAddress, out int port) {
/*
* stateConnection string has the following format:
*
* "tcpip=<server>:<port>"
* "tcpip=[IPv6-address]:port", per RFC 3986, Sec. 3.2.2
*/
// chop off the "tcpip=" part
if (!stateConnectionString.StartsWith("tcpip=", StringComparison.Ordinal)) {
throw new ArgumentException("stateConnectionString");
}
stateConnectionString = stateConnectionString.Substring("tcpip=".Length);
// is this an IPv6 address?
Match ipv6RegexMatch = _ipv6ConnectionStringFormat.Match(stateConnectionString);
if (ipv6RegexMatch != null && ipv6RegexMatch.Success) {
string ipv6AddressString = ipv6RegexMatch.Groups["ipv6Address"].Value;
IPAddress ipv6Address = IPAddress.Parse(ipv6AddressString);
if (ipv6Address.AddressFamily != AddressFamily.InterNetworkV6) {
throw new ArgumentException("stateConnectionString");
}
server = ipv6AddressString;
serverIsIPv6NumericAddress = true;
port = UInt16.Parse(ipv6RegexMatch.Groups["port"].Value, CultureInfo.InvariantCulture);
return;
}
// not an IPv6 address; assume "host:port"
string[] parts = stateConnectionString.Split(':');
if (parts.Length != 2) {
throw new ArgumentException("stateConnectionString");
}
server = parts[0];
serverIsIPv6NumericAddress = false;
port = UInt16.Parse(parts[1], CultureInfo.InvariantCulture);
}
internal static HttpException CreateConnectionException(string server, int port, int hr) {
if (s_usePartition) {
return new HttpException(
SR.GetString(SR.Cant_make_session_request_partition_resolver,
s_configPartitionResolverType, server, port.ToString(CultureInfo.InvariantCulture)), hr);
}
else {
return new HttpException(
SR.GetString(SR.Cant_make_session_request), hr);
}
}
public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback) {
return false;
}
public override void Dispose() {
}
public override void InitializeRequest(HttpContext context) {
if (s_usePartition) {
// For multiple partition case, the connection info can change from request to request
Debug.Assert(_partitionResolver != null);
_partitionInfo = null;
}
}
void MakeRequest(
UnsafeNativeMethods.StateProtocolVerb verb,
String id,
UnsafeNativeMethods.StateProtocolExclusive exclusiveAccess,
int extraFlags,
int timeout,
int lockCookie,
byte[] buf,
int cb,
int networkTimeout,
out UnsafeNativeMethods.SessionNDMakeRequestResults results) {
int hr;
string uri;
OutOfProcConnection conn = null;
HandleRef socketHandle;
bool checkVersion = false;
Debug.Assert(timeout <= SessionStateModule.MAX_CACHE_BASED_TIMEOUT_MINUTES, "item.Timeout <= SessionStateModule.MAX_CACHE_BASED_TIMEOUT_MINUTES");
SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
if (_partitionInfo == null) {
Debug.Assert(s_partitionManager != null);
Debug.Assert(_partitionResolver != null);
_partitionInfo = (StateServerPartitionInfo)s_partitionManager.GetPartition(_partitionResolver, id);
// If its still null, we give up
if (_partitionInfo == null) {
throw new HttpException(SR.GetString(SR.Bad_partition_resolver_connection_string, "PartitionManager"));
}
}
// Need to make sure we dispose the connection if anything goes wrong
try {
conn = (OutOfProcConnection)_partitionInfo.RetrieveResource();
if (conn != null) {
socketHandle = new HandleRef(this, conn._socketHandle.Handle);
}
else {
socketHandle = new HandleRef(this, INVALID_SOCKET);
}
if (_partitionInfo.StateServerVersion == -1) {
// We don't need locking here because it's okay to have two
// requests initializing s_stateServerVersion.
checkVersion = true;
}
Debug.Trace("OutOfProcSessionStateStoreMakeRequest",
"Calling MakeRequest, " +
"socket=" + (IntPtr)socketHandle.Handle +
"verb=" + verb +
" id=" + id +
" exclusiveAccess=" + exclusiveAccess +
" timeout=" + timeout +
" buf=" + ((buf != null) ? "non-null" : "null") +
" cb=" + cb +
" checkVersion=" + checkVersion +
" extraFlags=" + extraFlags);
// Have to UrlEncode id because it may contain non-URL-safe characters
uri = HttpUtility.UrlEncode(s_uribase + id);
hr = UnsafeNativeMethods.SessionNDMakeRequest(
socketHandle, _partitionInfo.Server, _partitionInfo.Port, _partitionInfo.ServerIsIPv6NumericAddress /* forceIPv6 */, networkTimeout, verb, uri,
exclusiveAccess, extraFlags, timeout, lockCookie,
buf, cb, checkVersion, out results);
Debug.Trace("OutOfProcSessionStateStoreMakeRequest", "MakeRequest returned: " +
"hr=" + hr +
" socket=" + (IntPtr)results.socket +
" httpstatus=" + results.httpStatus +
" timeout=" + results.timeout +
" contentlength=" + results.contentLength +
" uri=" + (IntPtr)results.content +
" lockCookie=" + results.lockCookie +
" lockDate=" + string.Format("{0:x}", results.lockDate) +
" lockAge=" + results.lockAge +
" stateServerMajVer=" + results.stateServerMajVer +
" actionFlags=" + results.actionFlags);
if (conn != null) {
if (results.socket == INVALID_SOCKET) {
conn.Detach();
conn = null;
}
else if (results.socket != socketHandle.Handle) {
// The original socket is no good. We've got a new one.
// Pleae note that EnsureConnected has closed the bad
// one already.
conn._socketHandle = new HandleRef(this, results.socket);
}
}
else if (results.socket != INVALID_SOCKET) {
conn = new OutOfProcConnection(results.socket);
}
if (conn != null) {
_partitionInfo.StoreResource(conn);
}
}
catch {
// We just need to dispose the connection if anything bad happened
if (conn != null) {
conn.Dispose();
}
throw;
}
if (hr != 0) {
HttpException e = CreateConnectionException(_partitionInfo.Server, _partitionInfo.Port, hr);
string phase = null;
switch (results.lastPhase) {
case (int)UnsafeNativeMethods.SessionNDMakeRequestPhase.Initialization:
phase = SR.GetString(SR.State_Server_detailed_error_phase0);
break;
case (int)UnsafeNativeMethods.SessionNDMakeRequestPhase.Connecting:
phase = SR.GetString(SR.State_Server_detailed_error_phase1);
break;
case (int)UnsafeNativeMethods.SessionNDMakeRequestPhase.SendingRequest:
phase = SR.GetString(SR.State_Server_detailed_error_phase2);
break;
case (int)UnsafeNativeMethods.SessionNDMakeRequestPhase.ReadingResponse:
phase = SR.GetString(SR.State_Server_detailed_error_phase3);
break;
default:
Debug.Assert(false, "Unknown results.lastPhase: " + results.lastPhase);
break;
}
WebBaseEvent.RaiseSystemEvent(SR.GetString(SR.State_Server_detailed_error,
phase,
"0x" + hr.ToString("X08", CultureInfo.InvariantCulture),
cb.ToString(CultureInfo.InvariantCulture)),
this, WebEventCodes.WebErrorOtherError, WebEventCodes.StateServerConnectionError, e);
throw e;
}
if (results.httpStatus == 400) {
if (s_usePartition) {
throw new HttpException(
SR.GetString(SR.Bad_state_server_request_partition_resolver,
s_configPartitionResolverType, _partitionInfo.Server, _partitionInfo.Port.ToString(CultureInfo.InvariantCulture)));
}
else {
throw new HttpException(
SR.GetString(SR.Bad_state_server_request));
}
}
if (checkVersion) {
_partitionInfo.StateServerVersion = results.stateServerMajVer;
if (_partitionInfo.StateServerVersion < WHIDBEY_MAJOR_VERSION) {
// We won't work with versions lower than Whidbey
if (s_usePartition) {
throw new HttpException(
SR.GetString(SR.Need_v2_State_Server_partition_resolver,
s_configPartitionResolverType, _partitionInfo.Server, _partitionInfo.Port.ToString(CultureInfo.InvariantCulture)));
}
else {
throw new HttpException(
SR.GetString(SR.Need_v2_State_Server));
}
}
}
}
[SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)]
internal SessionStateStoreData DoGet(HttpContext context,
String id,
UnsafeNativeMethods.StateProtocolExclusive exclusiveAccess,
out bool locked,
out TimeSpan lockAge,
out object lockId,
out SessionStateActions actionFlags) {
SessionStateStoreData item = null;
UnmanagedMemoryStream stream = null;
int contentLength;
UnsafeNativeMethods.SessionNDMakeRequestResults results;
// Set default return values
locked = false;
lockId = null;
lockAge = TimeSpan.Zero;
actionFlags = 0;
results.content = IntPtr.Zero;
try {
MakeRequest(UnsafeNativeMethods.StateProtocolVerb.GET,
id, exclusiveAccess, 0, 0, 0,
null, 0, s_networkTimeout, out results);
switch (results.httpStatus) {
case 200:
/* item found, deserialize it */
contentLength = results.contentLength;
if (contentLength > 0) {
try {
unsafe {
stream = new UnmanagedMemoryStream((byte*)results.content, contentLength);
}
item = SessionStateUtility.DeserializeStoreData(context, stream, s_configCompressionEnabled);
}
finally {
if(stream != null) {
stream.Close();
}
}
lockId = results.lockCookie;
actionFlags = (SessionStateActions) results.actionFlags;
}
break;
case 423:
/* state locked, return lock information */
if (0 <= results.lockAge) {
if (results.lockAge < Sec.ONE_YEAR) {
lockAge = new TimeSpan(0, 0, results.lockAge);
}
else {
lockAge = TimeSpan.Zero;
}
}
else {
DateTime now = DateTime.Now;
if (0 < results.lockDate && results.lockDate < now.Ticks) {
lockAge = now - new DateTime(results.lockDate);
}
else {
lockAge = TimeSpan.Zero;
}
}
locked = true;
lockId = results.lockCookie;
Debug.Assert((results.actionFlags & (int)SessionStateActions.InitializeItem) == 0,
"(results.actionFlags & (int)SessionStateActions.InitializeItem) == 0; uninitialized item cannot be locked");
break;
}
}
finally {
if (results.content != IntPtr.Zero) {
UnsafeNativeMethods.SessionNDFreeBody(new HandleRef(this, results.content));
}
}
return item;
}
public override SessionStateStoreData GetItem(HttpContext context,
String id,
out bool locked,
out TimeSpan lockAge,
out object lockId,
out SessionStateActions actionFlags) {
Debug.Trace("OutOfProcSessionStateStore", "Calling Get, id=" + id);
return DoGet(context, id, UnsafeNativeMethods.StateProtocolExclusive.NONE,
out locked, out lockAge, out lockId, out actionFlags);
}
public override SessionStateStoreData GetItemExclusive(HttpContext context,
String id,
out bool locked,
out TimeSpan lockAge,
out object lockId,
out SessionStateActions actionFlags) {
Debug.Trace("OutOfProcSessionStateStore", "Calling GetExlusive, id=" + id);
return DoGet(context, id, UnsafeNativeMethods.StateProtocolExclusive.ACQUIRE,
out locked, out lockAge, out lockId, out actionFlags);
}
public override void ReleaseItemExclusive(HttpContext context,
String id,
object lockId) {
Debug.Assert(lockId != null, "lockId != null");
UnsafeNativeMethods.SessionNDMakeRequestResults results;
int lockCookie = (int)lockId;
Debug.Trace("OutOfProcSessionStateStore", "Calling ReleaseExclusive, id=" + id);
MakeRequest(UnsafeNativeMethods.StateProtocolVerb.GET, id,
UnsafeNativeMethods.StateProtocolExclusive.RELEASE, 0, 0,
lockCookie, null, 0, s_networkTimeout, out results);
}
public override void SetAndReleaseItemExclusive(HttpContext context,
String id,
SessionStateStoreData item,
object lockId,
bool newItem) {
UnsafeNativeMethods.SessionNDMakeRequestResults results;
byte[] buf;
int length;
int lockCookie;
Debug.Assert(item.Items != null, "item.Items != null");
Debug.Assert(item.StaticObjects != null, "item.StaticObjects != null");
Debug.Trace("OutOfProcSessionStateStore", "Calling Set, id=" + id + " sessionItems=" + item.Items + " timeout=" + item.Timeout);
try {
SessionStateUtility.SerializeStoreData(item, 0, out buf, out length, s_configCompressionEnabled);
}
catch {
if (!newItem) {
((SessionStateStoreProviderBase)this).ReleaseItemExclusive(context, id, lockId);
}
throw;
}
// Save it to the store
if (lockId == null) {
lockCookie = 0;
}
else {
lockCookie = (int)lockId;
}
MakeRequest(UnsafeNativeMethods.StateProtocolVerb.PUT, id,
UnsafeNativeMethods.StateProtocolExclusive.NONE, 0, item.Timeout, lockCookie,
buf, length, s_networkTimeout, out results);
}
public override void RemoveItem(HttpContext context,
String id,
object lockId,
SessionStateStoreData item) {
Debug.Assert(lockId != null, "lockId != null");
Debug.Trace("OutOfProcSessionStateStore", "Calling Remove, id=" + id);
UnsafeNativeMethods.SessionNDMakeRequestResults results;
int lockCookie = (int)lockId;
MakeRequest(UnsafeNativeMethods.StateProtocolVerb.DELETE, id,
UnsafeNativeMethods.StateProtocolExclusive.NONE, 0, 0, lockCookie,
null, 0, s_networkTimeout, out results);
}
public override void ResetItemTimeout(HttpContext context, String id) {
UnsafeNativeMethods.SessionNDMakeRequestResults results;
Debug.Trace("OutOfProcSessionStateStore", "Calling ResetTimeout, id=" + id);
MakeRequest(UnsafeNativeMethods.StateProtocolVerb.HEAD, id,
UnsafeNativeMethods.StateProtocolExclusive.NONE, 0, 0, 0,
null, 0, s_networkTimeout, out results);
}
public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
{
Debug.Assert(timeout <= SessionStateModule.MAX_CACHE_BASED_TIMEOUT_MINUTES, "item.Timeout <= SessionStateModule.MAX_CACHE_BASED_TIMEOUT_MINUTES");
return SessionStateUtility.CreateLegitStoreData(context, null, null, timeout);
}
public override void CreateUninitializedItem(HttpContext context, String id, int timeout) {
UnsafeNativeMethods.SessionNDMakeRequestResults results;
byte[] buf;
int length;
Debug.Trace("OutOfProcSessionStateStore", "Calling CreateUninitializedItem, id=" + id + " timeout=" + timeout);
// Create an empty item
SessionStateUtility.SerializeStoreData(CreateNewStoreData(context, timeout), 0, out buf, out length, s_configCompressionEnabled);
// Save it to the store
MakeRequest(UnsafeNativeMethods.StateProtocolVerb.PUT, id,
UnsafeNativeMethods.StateProtocolExclusive.NONE,
(int)SessionStateItemFlags.Uninitialized, timeout, 0,
buf, length, s_networkTimeout, out results);
}
// Called during EndRequest event
public override void EndRequest(HttpContext context) {
}
class StateServerPartitionInfo : PartitionInfo {
string _server;
bool _serverIsIPv6NumericAddress;
int _port;
int _stateServerVersion;
internal StateServerPartitionInfo(ResourcePool rpool, string server, bool serverIsIPv6NumericAddress, int port) : base(rpool) {
_server = server;
_serverIsIPv6NumericAddress = serverIsIPv6NumericAddress;
_port = port;
_stateServerVersion = -1;
Debug.Trace("PartitionInfo", "Created a new info, server=" + server + ", port=" + port);
}
internal string Server {
get { return _server; }
}
internal bool ServerIsIPv6NumericAddress {
get { return _serverIsIPv6NumericAddress; }
}
internal int Port {
get { return _port; }
}
internal int StateServerVersion {
get { return _stateServerVersion; }
set { _stateServerVersion = value; }
}
protected override string TracingPartitionString {
get {
// only add the brackets if the server is an IPv6 address, per the URI specification
string formatString = (ServerIsIPv6NumericAddress) ? "[{0}]:{1}" : "{0}:{1}";
return String.Format(CultureInfo.InvariantCulture, formatString, Server, Port);
}
}
}
class OutOfProcConnection : IDisposable {
internal HandleRef _socketHandle;
internal OutOfProcConnection(IntPtr socket) {
Debug.Assert(socket != OutOfProcSessionStateStore.INVALID_SOCKET,
"socket != OutOfProcSessionStateStore.INVALID_SOCKET");
_socketHandle = new HandleRef(this, socket);
PerfCounters.IncrementCounter(AppPerfCounter.SESSION_STATE_SERVER_CONNECTIONS);
}
~OutOfProcConnection() {
Dispose(false);
}
public void Dispose() {
Debug.Trace("ResourcePool", "Disposing OutOfProcConnection");
Dispose(true);
System.GC.SuppressFinalize(this);
}
private void Dispose(bool dummy) {
if (_socketHandle.Handle != OutOfProcSessionStateStore.INVALID_SOCKET) {
UnsafeNativeMethods.SessionNDCloseConnection(_socketHandle);
_socketHandle = new HandleRef(this, OutOfProcSessionStateStore.INVALID_SOCKET);
PerfCounters.DecrementCounter(AppPerfCounter.SESSION_STATE_SERVER_CONNECTIONS);
}
}
internal void Detach() {
_socketHandle = new HandleRef(this, OutOfProcSessionStateStore.INVALID_SOCKET);
}
}
}
}
|