2 // System.Web.SessionState.SesionStateModule
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 // Stefan Görling (stefan@gorling.se)
7 // Jackson Harper (jackson@ximian.com)
8 // Marek Habersack (grendello@gmail.com)
10 // Copyright (C) 2002-2006 Novell, Inc (http://www.novell.com)
11 // (C) 2003 Stefan Görling (http://www.gorling.se)
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 using System.Collections.Specialized;
35 using System.ComponentModel;
36 using System.Web.Configuration;
37 using System.Web.Caching;
38 using System.Web.Util;
39 using System.Security.Permissions;
40 using System.Threading;
41 using System.Configuration;
42 using System.Diagnostics;
44 namespace System.Web.SessionState
46 // CAS - no InheritanceDemand here as the class is sealed
47 [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
48 public sealed class SessionStateModule : IHttpModule
52 public readonly HttpContext Context;
53 public readonly AutoResetEvent AutoEvent;
54 public readonly string SessionId;
55 public readonly bool IsReadOnly;
57 public CallbackState (HttpContext context, AutoResetEvent e, string sessionId, bool isReadOnly) {
58 this.Context = context;
60 this.SessionId = sessionId;
61 this.IsReadOnly = isReadOnly;
65 internal const string HeaderName = "AspFilterSessionId";
66 internal const string CookielessFlagName = "_SessionIDManager_IsCookieLess";
68 static readonly object startEvent = new object ();
69 static readonly object endEvent = new object ();
71 SessionStateSection config;
73 SessionStateStoreProviderBase handler;
74 ISessionIDManager idManager;
75 bool supportsExpiration;
81 TimeSpan storeLockAge;
83 SessionStateActions storeSessionAction;
87 SessionStateStoreData storeData;
88 HttpSessionStateContainer container;
91 TimeSpan executionTimeout;
92 //int executionTimeoutMS;
94 EventHandlerList events = new EventHandlerList ();
96 public event EventHandler Start {
97 add { events.AddHandler (startEvent, value); }
98 remove { events.RemoveHandler (startEvent, value); }
101 // This event is public, but only Session_[On]End in global.asax will be invoked if present.
102 public event EventHandler End {
103 add { events.AddHandler (endEvent, value); }
104 remove { events.RemoveHandler (endEvent, value); }
107 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
108 public SessionStateModule () {
111 public void Dispose () {
112 app.BeginRequest -= new EventHandler (OnBeginRequest);
113 app.AcquireRequestState -= new EventHandler (OnAcquireRequestState);
114 app.ReleaseRequestState -= new EventHandler (OnReleaseRequestState);
115 app.EndRequest -= new EventHandler (OnEndRequest);
119 [EnvironmentPermission (SecurityAction.Assert, Read = "MONO_XSP_STATIC_SESSION")]
120 public void Init (HttpApplication app)
122 config = (SessionStateSection) WebConfigurationManager.GetSection ("system.web/sessionState");
124 ProviderSettings settings;
125 switch (config.Mode) {
126 case SessionStateMode.Custom:
127 settings = config.Providers [config.CustomProvider];
128 if (settings == null)
129 throw new HttpException (String.Format ("Cannot find '{0}' provider.", config.CustomProvider));
131 case SessionStateMode.Off:
135 config = new SessionStateSection ();
136 config.Mode = SessionStateMode.Custom;
137 config.CustomProvider = "ServletSessionStateStore";
138 config.SessionIDManagerType = "Mainsoft.Web.SessionState.ServletSessionIDManager";
139 config.Providers.Add (new ProviderSettings ("ServletSessionStateStore", "Mainsoft.Web.SessionState.ServletSessionStateStoreProvider"));
140 goto case SessionStateMode.Custom;
142 case SessionStateMode.InProc:
143 settings = new ProviderSettings (null, typeof (SessionInProcHandler).AssemblyQualifiedName);
146 case SessionStateMode.SQLServer:
147 settings = new ProviderSettings (null, typeof (SessionSQLServerHandler).AssemblyQualifiedName);
150 case SessionStateMode.StateServer:
151 settings = new ProviderSettings (null, typeof (SessionStateServerHandler).AssemblyQualifiedName);
155 throw new NotImplementedException (String.Format ("The mode '{0}' is not implemented.", config.Mode));
160 handler = (SessionStateStoreProviderBase) ProvidersHelper.InstantiateProvider (settings, typeof (SessionStateStoreProviderBase));
162 if (String.IsNullOrEmpty(config.SessionIDManagerType)) {
163 idManager = new SessionIDManager ();
165 Type idManagerType = HttpApplication.LoadType (config.SessionIDManagerType, true);
166 idManager = (ISessionIDManager)Activator.CreateInstance (idManagerType);
170 idManager.Initialize ();
171 } catch (Exception ex) {
172 throw new HttpException ("Failed to initialize session ID manager.", ex);
175 supportsExpiration = handler.SetItemExpireCallback (OnSessionExpired);
176 HttpRuntimeSection runtime = HttpRuntime.Section;
177 executionTimeout = runtime.ExecutionTimeout;
178 //executionTimeoutMS = executionTimeout.Milliseconds;
182 app.BeginRequest += new EventHandler (OnBeginRequest);
183 app.AcquireRequestState += new EventHandler (OnAcquireRequestState);
184 app.ReleaseRequestState += new EventHandler (OnReleaseRequestState);
185 app.EndRequest += new EventHandler (OnEndRequest);
188 internal static bool IsCookieLess (HttpContext context, SessionStateSection config) {
189 if (config.Cookieless == HttpCookieMode.UseCookies)
191 if (config.Cookieless == HttpCookieMode.UseUri)
193 object cookieless = context.Items [CookielessFlagName];
194 if (cookieless == null)
196 return (bool) cookieless;
199 void OnBeginRequest (object o, EventArgs args)
201 HttpApplication application = (HttpApplication) o;
202 HttpContext context = application.Context;
203 string file_path = context.Request.FilePath;
204 string base_path = VirtualPathUtility.GetDirectory (file_path);
205 string id = UrlUtils.GetSessionId (base_path);
210 string new_path = UrlUtils.RemoveSessionId (base_path, file_path);
211 context.Request.SetFilePath (new_path);
212 context.Request.SetHeader (HeaderName, id);
213 context.Response.SetAppPathModifier (id);
216 void OnAcquireRequestState (object o, EventArgs args) {
217 Trace.WriteLine ("SessionStateModule.OnAcquireRequestState (hash " + this.GetHashCode ().ToString ("x") + ")");
218 HttpApplication application = (HttpApplication) o;
219 HttpContext context = application.Context;
221 if (!(context.Handler is IRequiresSessionState)) {
222 Trace.WriteLine ("Handler (" + context.Handler + ") does not require session state");
225 bool isReadOnly = (context.Handler is IReadOnlySessionState);
227 bool supportSessionIDReissue;
228 if (idManager.InitializeRequest (context, false, out supportSessionIDReissue))
229 return; // Redirected, will come back here in a while
230 string sessionId = idManager.GetSessionID (context);
232 handler.InitializeRequest (context);
234 storeData = GetStoreData (context, sessionId, isReadOnly);
237 if (storeData == null && !storeLocked) {
239 sessionId = idManager.CreateSessionID (context);
240 Trace.WriteLine ("New session ID allocated: " + sessionId);
243 idManager.SaveSessionID (context, sessionId, out redirected, out cookieAdded);
245 if (supportSessionIDReissue)
246 handler.CreateUninitializedItem (context, sessionId, (int)config.Timeout.TotalMinutes);
247 context.Response.End ();
251 storeData = handler.CreateNewStoreData (context, (int)config.Timeout.TotalMinutes);
253 else if (storeData == null && storeLocked) {
254 WaitForStoreUnlock (context, sessionId, isReadOnly);
256 else if (storeData != null &&
258 storeSessionAction == SessionStateActions.InitializeItem &&
259 IsCookieLess (context, config)) {
260 storeData = handler.CreateNewStoreData (context, (int)config.Timeout.TotalMinutes);
263 container = CreateContainer (sessionId, storeData, storeIsNew, isReadOnly);
264 SessionStateUtility.AddHttpSessionStateToContext (app.Context, container);
267 HttpSessionState hss = app.Session;
270 storeData.Timeout = hss.Timeout;
273 // Whenever a container is abandoned, we temporarily disable the expire call back.
274 // So in this case we are quite sure we have a brand new container, so we make sure it works again.
275 supportsExpiration = handler.SetItemExpireCallback (OnSessionExpired);
278 void OnReleaseRequestState (object o, EventArgs args) {
280 Trace.WriteLine ("SessionStateModule.OnReleaseRequestState (hash " + this.GetHashCode ().ToString ("x") + ")");
282 HttpApplication application = (HttpApplication) o;
283 HttpContext context = application.Context;
284 if (!(context.Handler is IRequiresSessionState))
287 Trace.WriteLine ("\tsessionId == " + container.SessionID);
288 Trace.WriteLine ("\trequest path == " + context.Request.FilePath);
289 Trace.WriteLine ("\tHandler (" + context.Handler + ") requires session state");
291 if (!container.IsAbandoned) {
292 Trace.WriteLine ("\tnot abandoned");
293 if (!container.IsReadOnly) {
294 Trace.WriteLine ("\tnot read only, storing and releasing");
295 handler.SetAndReleaseItemExclusive (context, container.SessionID, storeData, storeLockId, storeIsNew);
298 Trace.WriteLine ("\tread only, releasing");
299 handler.ReleaseItemExclusive (context, container.SessionID, storeLockId);
301 handler.ResetItemTimeout (context, container.SessionID);
304 handler.ReleaseItemExclusive (context, container.SessionID, storeLockId);
305 handler.RemoveItem (context, container.SessionID, storeLockId, storeData);
306 if (supportsExpiration)
311 // Make sure the expiration handler is not called after we will have raised
312 // the session end event.
313 handler.SetItemExpireCallback (null);
315 SessionStateUtility.RaiseSessionEnd (container, this, args);
317 SessionStateUtility.RemoveHttpSessionStateFromContext (context);
325 void OnEndRequest (object o, EventArgs args) {
329 if (container != null)
330 OnReleaseRequestState (o, args);
332 HttpApplication application = o as HttpApplication;
333 if (application == null)
336 handler.EndRequest (application.Context);
339 SessionStateStoreData GetStoreData (HttpContext context, string sessionId, bool isReadOnly) {
340 SessionStateStoreData item;
341 item = (isReadOnly) ?
342 handler.GetItem (context,
347 out storeSessionAction)
349 handler.GetItemExclusive (context,
354 out storeSessionAction);
355 if (storeLockId == null)
361 void WaitForStoreUnlock (HttpContext context, string sessionId, bool isReadonly) {
362 AutoResetEvent are = new AutoResetEvent (false);
363 TimerCallback tc = new TimerCallback (StoreUnlockWaitCallback);
364 CallbackState cs = new CallbackState (context, are, sessionId, isReadonly);
365 using (Timer timer = new Timer (tc, cs, 500, 500)) {
367 are.WaitOne (executionTimeout, false);
375 void StoreUnlockWaitCallback (object s) {
376 CallbackState state = (CallbackState) s;
378 SessionStateStoreData item = GetStoreData (state.Context, state.SessionId, state.IsReadOnly);
380 if (item == null && storeLocked && (storeLockAge > executionTimeout)) {
381 handler.ReleaseItemExclusive (state.Context, state.SessionId, storeLockId);
382 storeData = null; // Create new state
383 state.AutoEvent.Set ();
385 else if (item != null && !storeLocked) {
387 state.AutoEvent.Set ();
391 HttpSessionStateContainer CreateContainer (string sessionId, SessionStateStoreData data, bool isNew, bool isReadOnly) {
393 return new HttpSessionStateContainer (
394 sessionId, null, null, 0, isNew,
395 config.Cookieless, config.Mode, isReadOnly);
397 return new HttpSessionStateContainer (
408 void OnSessionExpired (string id, SessionStateStoreData item) {
409 SessionStateUtility.RaiseSessionEnd (
410 CreateContainer (id, item, false, true),
411 this, EventArgs.Empty);
414 void OnSessionStart () {
415 EventHandler eh = events [startEvent] as EventHandler;
417 eh (this, EventArgs.Empty);