aboutsummaryrefslogtreecommitdiff
path: root/libs/VNLib.Plugins.Sessions.VNCache/src/WebSessionProvider.cs
blob: d2b1e7e4444f65fd56743cc293ff37f9c6df3cae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/*
* Copyright (c) 2023 Vaughn Nugent
* 
* Library: VNLib
* Package: VNLib.Plugins.Essentials.Sessions.VNCache
* File: WebSessionProvider.cs 
*
* WebSessionProvider.cs is part of VNLib.Plugins.Essentials.Sessions.VNCache which is part of the larger 
* VNLib collection of libraries and utilities.
*
* VNLib.Plugins.Essentials.Sessions.VNCache is free software: you can redistribute it and/or modify 
* it under the terms of the GNU Affero General Public License as 
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* VNLib.Plugins.Essentials.Sessions.VNCache is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program.  If not, see https://www.gnu.org/licenses/.
*/

using System;
using System.Threading;
using System.Threading.Tasks;

using VNLib.Net.Http;
using VNLib.Utils.Extensions;
using VNLib.Plugins.Essentials;
using VNLib.Plugins.Essentials.Sessions;
using VNLib.Plugins.Extensions.Loading;

namespace VNLib.Plugins.Sessions.VNCache
{

    [ConfigurationName(WebSessionProviderEntry.WEB_SESSION_CONFIG)]
    internal sealed class WebSessionProvider : ISessionProvider
    {
        private static readonly SessionHandle _vf =  new (null, FileProcessArgs.VirtualSkip, null);

        private readonly TimeSpan _validFor;
        private readonly WebSessionStore _sessions;
        private readonly uint _maxConnections;

        private uint _waitingConnections;

        public bool IsConnected => _sessions.IsConnected;

        public WebSessionProvider(PluginBase plugin, IConfigScope config)
        {
            _validFor = config["valid_for_sec"].GetTimeSpan(TimeParseType.Seconds);
            _maxConnections = config["max_waiting_connections"].GetUInt32();

            //Init session provider
            _sessions = plugin.GetOrCreateSingleton<WebSessionStore>();
        }

        private SessionHandle PostProcess(WebSession? session)
        {
            if (session == null)
            {
                return SessionHandle.Empty;
            }

            //Make sure the session has not expired yet
            if (session.Created.Add(_validFor) < DateTimeOffset.UtcNow)
            {
                //Invalidate the session, so its technically valid for this request, but will be cleared on this handle close cycle
                session.Invalidate();

                //Clear basic login status
                session.Token = null;
                session.UserID = null;
                session.Privilages = 0;
                session.SetLoginToken(null);
            }

            return new SessionHandle(session, OnSessionReleases);
        }

        private ValueTask OnSessionReleases(ISession session, IHttpEvent entity) => _sessions.ReleaseSessionAsync((WebSession)session, entity);

        public ValueTask<SessionHandle> GetSessionAsync(IHttpEvent entity, CancellationToken cancellationToken)
        {
            //Limit max number of waiting clients and make sure were connected
            if (!_sessions.IsConnected || _waitingConnections > _maxConnections)
            {
                //Set 503 for temporary unavail
                entity.CloseResponse(System.Net.HttpStatusCode.ServiceUnavailable);
                return ValueTask.FromResult(_vf);
            }

            ValueTask<WebSession?> result = _sessions.GetSessionAsync(entity, cancellationToken);

            if (result.IsCompleted)
            {
                WebSession? session = result.GetAwaiter().GetResult();

                //Post process and get handle for session
                SessionHandle handle = PostProcess(session);

                return ValueTask.FromResult(handle);
            }
            else
            {
                return new(AwaitAsyncGet(result));
            }
        }

        private async Task<SessionHandle> AwaitAsyncGet(ValueTask<WebSession?> async)
        {
            //Inct wait count while async waiting
            _waitingConnections++;
            try
            {
                //await the session
                WebSession? session = await async.ConfigureAwait(false);

                //return empty session handle if the session could not be found
                return PostProcess(session);
            }
            finally
            {
                _waitingConnections--;
            }
        }
    }
}