aboutsummaryrefslogtreecommitdiff
path: root/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionStore.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionStore.cs')
-rw-r--r--Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionStore.cs127
1 files changed, 127 insertions, 0 deletions
diff --git a/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionStore.cs b/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionStore.cs
new file mode 100644
index 0000000..15c3002
--- /dev/null
+++ b/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionStore.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+using VNLib.Hashing;
+using VNLib.Net.Http;
+using VNLib.Net.Sessions;
+using VNLib.Utils;
+using VNLib.Utils.Async;
+using VNLib.Utils.Extensions;
+using VNLib.Plugins.Essentials.Extensions;
+
+
+namespace VNLib.Plugins.Essentials.Sessions.Memory
+{
+
+ /// <summary>
+ /// An <see cref="ISessionProvider"/> for in-process-memory backed sessions
+ /// </summary>
+ internal sealed class MemorySessionStore : ISessionProvider
+ {
+ private readonly Dictionary<string, MemorySession> SessionsStore;
+
+ internal readonly MemorySessionConfig Config;
+
+ public MemorySessionStore(MemorySessionConfig config)
+ {
+ Config = config;
+ SessionsStore = new(config.MaxAllowedSessions, StringComparer.Ordinal);
+ }
+
+ ///<inheritdoc/>
+ public async ValueTask<SessionHandle> GetSessionAsync(IHttpEvent entity, CancellationToken cancellationToken)
+ {
+
+ static ValueTask SessionHandleClosedAsync(ISession session, IHttpEvent ev)
+ {
+ return (session as MemorySession).UpdateAndRelease(true, ev);
+ }
+
+ //Check for previous session cookie
+ if (entity.Server.RequestCookies.TryGetNonEmptyValue(Config.SessionCookieID, out string sessionId))
+ {
+ //Try to get the old record or evict it
+ ERRNO result = SessionsStore.TryGetOrEvictRecord(sessionId, out MemorySession session);
+ if(result > 0)
+ {
+ //Valid, now wait for exclusive access
+ await session.WaitOneAsync(cancellationToken);
+ return new (session, SessionHandleClosedAsync);
+ }
+ //Continue creating a new session
+ }
+
+ //Dont service non browsers for new sessions
+ if (!entity.Server.IsBrowser())
+ {
+ return SessionHandle.Empty;
+ }
+
+ //try to cleanup expired records
+ SessionsStore.CollectRecords();
+ //Make sure there is enough room to add a new session
+ if (SessionsStore.Count >= Config.MaxAllowedSessions)
+ {
+ entity.Server.SetNoCache();
+ //Set 503 when full
+ entity.CloseResponse(System.Net.HttpStatusCode.ServiceUnavailable);
+ //Cannot service new session
+ return new(null, FileProcessArgs.VirtualSkip, null);
+ }
+ //Initialze a new session
+ MemorySession ms = new(entity.Server.GetTrustedIp(), this);
+ //Set session cookie
+ SetSessionCookie(entity, ms);
+ //Increment the semaphore
+ (ms as IWaitHandle).WaitOne();
+ //store the session in cache while holding semaphore, and set its expiration
+ SessionsStore.StoreRecord(ms.SessionID, ms, Config.SessionTimeout);
+ //Init new session handle
+ return new SessionHandle(ms, SessionHandleClosedAsync);
+ }
+
+ /// <summary>
+ /// Gets a new unique sessionid for sessions
+ /// </summary>
+ internal string NewSessionID => RandomHash.GetRandomHex((int)Config.SessionIdSizeBytes);
+
+ internal void UpdateRecord(string newSessId, MemorySession session)
+ {
+ lock (SessionsStore)
+ {
+ //Remove old record from the store
+ SessionsStore.Remove(session.SessionID);
+ //Insert the new session
+ SessionsStore.Add(newSessId, session);
+ }
+ }
+ /// <summary>
+ /// Sets a standard session cookie for an entity/connection
+ /// </summary>
+ /// <param name="entity">The entity to set the cookie on</param>
+ /// <param name="session">The session attached to the </param>
+ internal void SetSessionCookie(IHttpEvent entity, MemorySession session)
+ {
+ //Set session cookie
+ entity.Server.SetCookie(Config.SessionCookieID, session.SessionID, null, "/", Config.SessionTimeout, CookieSameSite.Lax, true, true);
+ }
+ /// <summary>
+ /// Evicts all sessions from the current store
+ /// </summary>
+ public void Cleanup()
+ {
+ //Expire all old records to cleanup all entires
+ this.SessionsStore.CollectRecords(DateTime.MaxValue);
+ }
+ /// <summary>
+ /// Collects all expired records from the current store
+ /// </summary>
+ public void GC()
+ {
+ //collect expired records
+ this.SessionsStore.CollectRecords();
+ }
+ }
+}