2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
4
using System ;
5
+ using System . Buffers ;
5
6
using System . IO ;
6
7
using System . Linq ;
7
8
using System . Net ;
8
9
using System . Net . Http ;
10
+ using System . Text . Json ;
11
+ using System . Text . Json . Serialization ;
9
12
using System . Threading ;
10
13
using System . Threading . Tasks ;
11
14
using Microsoft . AspNetCore . Http ;
16
19
17
20
namespace Microsoft . AspNetCore . SystemWebAdapters . SessionState . RemoteSession ;
18
21
19
- internal partial class RemoteAppSessionStateManager : ISessionManager
22
+ internal abstract partial class RemoteAppSessionStateManager : ISessionManager
20
23
{
21
- private readonly ISessionSerializer _serializer ;
22
- private readonly ILogger < RemoteAppSessionStateManager > _logger ;
23
- private readonly RemoteAppSessionStateClientOptions _options ;
24
- private readonly HttpClient _backchannelClient ;
24
+ private readonly ILogger _logger ;
25
25
26
26
public RemoteAppSessionStateManager (
27
27
ISessionSerializer serializer ,
28
28
IOptions < RemoteAppSessionStateClientOptions > options ,
29
29
IOptions < RemoteAppClientOptions > remoteAppClientOptions ,
30
- ILogger < RemoteAppSessionStateManager > logger )
30
+ ILogger logger )
31
31
{
32
- _options = options ? . Value ?? throw new ArgumentNullException ( nameof ( options ) ) ;
33
- _backchannelClient = remoteAppClientOptions ? . Value . BackchannelClient ?? throw new ArgumentNullException ( nameof ( remoteAppClientOptions ) ) ;
32
+ Options = options ? . Value ?? throw new ArgumentNullException ( nameof ( options ) ) ;
33
+ BackchannelClient = remoteAppClientOptions ? . Value . BackchannelClient ?? throw new ArgumentNullException ( nameof ( remoteAppClientOptions ) ) ;
34
34
_logger = logger ?? throw new ArgumentNullException ( nameof ( logger ) ) ;
35
- _serializer = serializer ?? throw new ArgumentNullException ( nameof ( serializer ) ) ;
35
+ Serializer = serializer ?? throw new ArgumentNullException ( nameof ( serializer ) ) ;
36
36
}
37
37
38
+ protected ISessionSerializer Serializer { get ; }
39
+
40
+ protected RemoteAppSessionStateClientOptions Options { get ; }
41
+
42
+ protected HttpClient BackchannelClient { get ; }
43
+
38
44
[ LoggerMessage ( EventId = 0 , Level = LogLevel . Debug , Message = "Loaded {Count} items from remote session state for session {SessionId}" ) ]
39
- private partial void LogSessionLoad ( int count , string sessionId ) ;
45
+ protected partial void LogSessionLoad ( int count , string sessionId ) ;
40
46
41
47
[ LoggerMessage ( EventId = 1 , Level = LogLevel . Error , Message = "Unable to load remote session state for session {SessionId}" ) ]
42
- private partial void LogRemoteSessionException ( Exception exc , string ? sessionId ) ;
48
+ protected partial void LogRemoteSessionException ( Exception exc , string ? sessionId ) ;
43
49
44
50
[ LoggerMessage ( EventId = 2 , Level = LogLevel . Trace , Message = "Received {StatusCode} response getting remote session state" ) ]
45
- private partial void LogRetrieveResponse ( HttpStatusCode statusCode ) ;
51
+ protected partial void LogRetrieveResponse ( HttpStatusCode statusCode ) ;
46
52
47
53
[ LoggerMessage ( EventId = 3 , Level = LogLevel . Trace , Message = "Received {StatusCode} response committing remote session state" ) ]
48
- private partial void LogCommitResponse ( HttpStatusCode statusCode ) ;
54
+ protected partial void LogCommitResponse ( HttpStatusCode statusCode ) ;
49
55
50
56
public async Task < ISessionState > CreateAsync ( HttpContextCore context , SessionAttribute metadata )
51
57
{
52
58
// If an existing remote session ID is present in the request, use its session ID.
53
59
// Otherwise, leave session ID null for now; it will be provided by the remote service
54
60
// when session data is loaded.
55
- var sessionId = context . Request . Cookies [ _options . CookieName ] ;
61
+ var sessionId = context . Request . Cookies [ Options . CookieName ] ;
56
62
57
63
try
58
64
{
@@ -70,103 +76,24 @@ public async Task<ISessionState> CreateAsync(HttpContextCore context, SessionAtt
70
76
}
71
77
}
72
78
73
- private async Task < ISessionState > GetSessionDataAsync ( string ? sessionId , bool readOnly , HttpContextCore callingContext , CancellationToken token )
74
- {
75
- // The request message is manually disposed at a later time
76
- #pragma warning disable CA2000 // Dispose objects before losing scope
77
- var req = new HttpRequestMessage ( HttpMethod . Get , _options . Path . Relative ) ;
78
- #pragma warning restore CA2000 // Dispose objects before losing scope
79
-
80
- AddSessionCookieToHeader ( req , sessionId ) ;
81
- AddReadOnlyHeader ( req , readOnly ) ;
82
-
83
- var response = await _backchannelClient . SendAsync ( req , HttpCompletionOption . ResponseHeadersRead , token ) ;
84
-
85
- LogRetrieveResponse ( response . StatusCode ) ;
86
-
87
- response . EnsureSuccessStatusCode ( ) ;
88
-
89
- var remoteSessionState = await _serializer . DeserializeAsync ( await response . Content . ReadAsStreamAsync ( token ) , token ) ;
90
-
91
- if ( remoteSessionState is null )
92
- {
93
- throw new InvalidOperationException ( "Could not retrieve remote session state" ) ;
94
- }
95
-
96
- // Propagate headers back to the caller since a new session ID may have been set
97
- // by the remote app if there was no session active previously or if the previous
98
- // session expired.
99
- PropagateHeaders ( response , callingContext , HeaderNames . SetCookie ) ;
100
-
101
- if ( remoteSessionState . IsReadOnly )
102
- {
103
- response . Dispose ( ) ;
104
- return remoteSessionState ;
105
- }
106
-
107
- return new RemoteSessionState ( remoteSessionState , response , SetOrReleaseSessionData ) ;
108
- }
109
-
110
- /// <summary>
111
- /// Commits changes to the server. Passing null <paramref name="state"/> will release the session lock but not update session data.
112
- /// </summary>
113
- private async Task SetOrReleaseSessionData ( ISessionState ? state , CancellationToken cancellationToken )
114
- {
115
- using var req = new HttpRequestMessage ( HttpMethod . Put , _options . Path . Relative ) ;
116
-
117
- if ( state is not null )
118
- {
119
- AddSessionCookieToHeader ( req , state . SessionID ) ;
120
- req . Content = new SerializedSessionHttpContent ( _serializer , state ) ;
121
- }
122
-
123
- using var response = await _backchannelClient . SendAsync ( req , cancellationToken ) ;
79
+ protected abstract Task < ISessionState > GetSessionDataAsync ( string ? sessionId , bool readOnly , HttpContextCore callingContext , CancellationToken token ) ;
124
80
125
- LogCommitResponse ( response . StatusCode ) ;
126
-
127
- response . EnsureSuccessStatusCode ( ) ;
128
- }
129
-
130
- private static void PropagateHeaders ( HttpResponseMessage responseMessage , HttpContextCore context , string cookieName )
81
+ protected static void PropagateHeaders ( HttpResponseMessage responseMessage , HttpContextCore context , string cookieName )
131
82
{
132
83
if ( context ? . Response is not null && responseMessage . Headers . TryGetValues ( cookieName , out var cookieValues ) )
133
84
{
134
85
context . Response . Headers . AppendList ( cookieName , cookieValues . ToArray ( ) ) ;
135
86
}
136
87
}
137
88
138
- private void AddSessionCookieToHeader ( HttpRequestMessage req , string ? sessionId )
89
+ protected void AddSessionCookieToHeader ( HttpRequestMessage req , string ? sessionId )
139
90
{
140
91
if ( ! string . IsNullOrEmpty ( sessionId ) )
141
92
{
142
- req . Headers . Add ( HeaderNames . Cookie , $ "{ _options . CookieName } ={ sessionId } ") ;
93
+ req . Headers . Add ( HeaderNames . Cookie , $ "{ Options . CookieName } ={ sessionId } ") ;
143
94
}
144
95
}
145
96
146
- private static void AddReadOnlyHeader ( HttpRequestMessage req , bool readOnly )
97
+ protected static void AddReadOnlyHeader ( HttpRequestMessage req , bool readOnly )
147
98
=> req . Headers . Add ( SessionConstants . ReadOnlyHeaderName , readOnly . ToString ( ) ) ;
148
-
149
- private class SerializedSessionHttpContent : HttpContent
150
- {
151
- private readonly ISessionSerializer _serializer ;
152
- private readonly ISessionState _state ;
153
-
154
- public SerializedSessionHttpContent ( ISessionSerializer serializer , ISessionState state )
155
- {
156
- _serializer = serializer ;
157
- _state = state ;
158
- }
159
-
160
- protected override Task SerializeToStreamAsync ( Stream stream , TransportContext ? context )
161
- => SerializeToStreamAsync ( stream , context , default ) ;
162
-
163
- protected override Task SerializeToStreamAsync ( Stream stream , TransportContext ? context , CancellationToken cancellationToken )
164
- => _serializer . SerializeAsync ( _state , stream , cancellationToken ) ;
165
-
166
- protected override bool TryComputeLength ( out long length )
167
- {
168
- length = 0 ;
169
- return false ;
170
- }
171
- }
172
99
}
0 commit comments