1
1
using System ;
2
2
using System . Collections . Concurrent ;
3
- using System . Collections . Generic ;
4
- using System . IO ;
5
3
using System . Net . WebSockets ;
6
- using System . Security . Cryptography ;
7
- using System . Text ;
8
4
using System . Threading . Tasks ;
9
- using Microsoft . AspNetCore . Builder ;
10
5
using Microsoft . AspNetCore . Http ;
11
6
using Microsoft . AspNetCore . Http . Features ;
12
- using Microsoft . AspNetCore . WebSockets ;
13
- using Microsoft . Extensions . DependencyInjection ;
14
- using Microsoft . Extensions . Options ;
15
- using Microsoft . Net . Http . Headers ;
16
7
17
8
namespace Components . TestServer
18
9
{
19
10
public class InterruptibleWebSocketFeature : IHttpWebSocketFeature
20
11
{
21
12
public InterruptibleWebSocketFeature (
22
- HttpContext httpContext ,
13
+ IHttpWebSocketFeature socketsFeature ,
23
14
string socketIdentifier ,
24
15
ConcurrentDictionary < string , InterruptibleWebSocket > registry )
25
16
{
26
- HttpContext = httpContext ;
17
+ OriginalFeature = socketsFeature ;
27
18
SocketIdentifier = socketIdentifier ;
28
- OriginalFeature = new UpgradeHandshake (
29
- httpContext ,
30
- httpContext . Features . Get < IHttpUpgradeFeature > ( ) ,
31
- httpContext . RequestServices . GetRequiredService < IOptions < WebSocketOptions > > ( ) . Value ) ;
32
19
Registry = registry ;
33
20
}
34
21
35
22
public bool IsWebSocketRequest => OriginalFeature . IsWebSocketRequest ;
36
23
37
- public HttpContext HttpContext { get ; }
38
24
public string SocketIdentifier { get ; }
39
25
40
26
private IHttpWebSocketFeature OriginalFeature { get ; }
27
+
41
28
public ConcurrentDictionary < string , InterruptibleWebSocket > Registry { get ; }
42
29
43
30
public async Task < WebSocket > AcceptAsync ( WebSocketAcceptContext context )
@@ -56,192 +43,5 @@ public async Task<WebSocket> AcceptAsync(WebSocketAcceptContext context)
56
43
return socket ;
57
44
} ) ;
58
45
}
59
-
60
- private class UpgradeHandshake : IHttpWebSocketFeature
61
- {
62
- public static readonly IEnumerable < string > NeededHeaders = new [ ]
63
- {
64
- HeaderNames . Upgrade ,
65
- HeaderNames . Connection ,
66
- HeaderNames . SecWebSocketKey ,
67
- HeaderNames . SecWebSocketVersion
68
- } ;
69
-
70
- private readonly HttpContext _context ;
71
- private readonly IHttpUpgradeFeature _upgradeFeature ;
72
- private readonly WebSocketOptions _options ;
73
- private bool ? _isWebSocketRequest ;
74
-
75
- public UpgradeHandshake ( HttpContext context , IHttpUpgradeFeature upgradeFeature , WebSocketOptions options )
76
- {
77
- _context = context ;
78
- _upgradeFeature = upgradeFeature ;
79
- _options = options ;
80
- }
81
-
82
- public bool IsWebSocketRequest
83
- {
84
- get
85
- {
86
- if ( _isWebSocketRequest == null )
87
- {
88
- if ( ! _upgradeFeature . IsUpgradableRequest )
89
- {
90
- _isWebSocketRequest = false ;
91
- }
92
- else
93
- {
94
- var headers = new List < KeyValuePair < string , string > > ( ) ;
95
- foreach ( string headerName in NeededHeaders )
96
- {
97
- foreach ( var value in _context . Request . Headers . GetCommaSeparatedValues ( headerName ) )
98
- {
99
- headers . Add ( new KeyValuePair < string , string > ( headerName , value ) ) ;
100
- }
101
- }
102
- _isWebSocketRequest = CheckSupportedWebSocketRequest ( _context . Request . Method , headers ) ;
103
- }
104
- }
105
- return _isWebSocketRequest . Value ;
106
- }
107
- }
108
-
109
- public static bool CheckSupportedWebSocketRequest ( string method , IEnumerable < KeyValuePair < string , string > > headers )
110
- {
111
- bool validUpgrade = false , validConnection = false , validKey = false , validVersion = false ;
112
-
113
- if ( ! string . Equals ( "GET" , method , StringComparison . OrdinalIgnoreCase ) )
114
- {
115
- return false ;
116
- }
117
-
118
- foreach ( var pair in headers )
119
- {
120
- if ( string . Equals ( HeaderNames . Connection , pair . Key , StringComparison . OrdinalIgnoreCase ) )
121
- {
122
- if ( string . Equals ( Constants . Headers . ConnectionUpgrade , pair . Value , StringComparison . OrdinalIgnoreCase ) )
123
- {
124
- validConnection = true ;
125
- }
126
- }
127
- else if ( string . Equals ( HeaderNames . Upgrade , pair . Key , StringComparison . OrdinalIgnoreCase ) )
128
- {
129
- if ( string . Equals ( Constants . Headers . UpgradeWebSocket , pair . Value , StringComparison . OrdinalIgnoreCase ) )
130
- {
131
- validUpgrade = true ;
132
- }
133
- }
134
- else if ( string . Equals ( HeaderNames . SecWebSocketVersion , pair . Key , StringComparison . OrdinalIgnoreCase ) )
135
- {
136
- if ( string . Equals ( Constants . Headers . SupportedVersion , pair . Value , StringComparison . OrdinalIgnoreCase ) )
137
- {
138
- validVersion = true ;
139
- }
140
- }
141
- else if ( string . Equals ( HeaderNames . SecWebSocketKey , pair . Key , StringComparison . OrdinalIgnoreCase ) )
142
- {
143
- validKey = IsRequestKeyValid ( pair . Value ) ;
144
- }
145
- }
146
-
147
- return validConnection && validUpgrade && validVersion && validKey ;
148
- }
149
-
150
- public static bool IsRequestKeyValid ( string value )
151
- {
152
- if ( string . IsNullOrWhiteSpace ( value ) )
153
- {
154
- return false ;
155
- }
156
- try
157
- {
158
- byte [ ] data = Convert . FromBase64String ( value ) ;
159
- return data . Length == 16 ;
160
- }
161
- catch ( Exception )
162
- {
163
- return false ;
164
- }
165
- }
166
-
167
- public async Task < WebSocket > AcceptAsync ( WebSocketAcceptContext acceptContext )
168
- {
169
- if ( ! IsWebSocketRequest )
170
- {
171
- throw new InvalidOperationException ( "Not a WebSocket request." ) ; // TODO: LOC
172
- }
173
-
174
- string subProtocol = null ;
175
- if ( acceptContext != null )
176
- {
177
- subProtocol = acceptContext . SubProtocol ;
178
- }
179
-
180
- TimeSpan keepAliveInterval = _options . KeepAliveInterval ;
181
- int receiveBufferSize = _options . ReceiveBufferSize ;
182
- var advancedAcceptContext = acceptContext as ExtendedWebSocketAcceptContext ;
183
- if ( advancedAcceptContext != null )
184
- {
185
- if ( advancedAcceptContext . ReceiveBufferSize . HasValue )
186
- {
187
- receiveBufferSize = advancedAcceptContext . ReceiveBufferSize . Value ;
188
- }
189
- if ( advancedAcceptContext . KeepAliveInterval . HasValue )
190
- {
191
- keepAliveInterval = advancedAcceptContext . KeepAliveInterval . Value ;
192
- }
193
- }
194
-
195
- string key = string . Join ( ", " , _context . Request . Headers [ HeaderNames . SecWebSocketKey ] ) ;
196
-
197
- GenerateResponseHeaders ( key , subProtocol , _context . Response . Headers ) ;
198
-
199
- Stream opaqueTransport = await _upgradeFeature . UpgradeAsync ( ) ; // Sets status code to 101
200
-
201
- return WebSocket . CreateFromStream ( opaqueTransport , isServer : true , subProtocol : subProtocol , keepAliveInterval : keepAliveInterval ) ;
202
- }
203
-
204
- public static void GenerateResponseHeaders ( string key , string subProtocol , IHeaderDictionary headers )
205
- {
206
- headers [ HeaderNames . Connection ] = Constants . Headers . ConnectionUpgrade ;
207
- headers [ HeaderNames . Upgrade ] = Constants . Headers . UpgradeWebSocket ;
208
- headers [ HeaderNames . SecWebSocketAccept ] = CreateResponseKey ( key ) ;
209
- if ( ! string . IsNullOrWhiteSpace ( subProtocol ) )
210
- {
211
- headers [ HeaderNames . SecWebSocketProtocol ] = subProtocol ;
212
- }
213
- }
214
-
215
- public static string CreateResponseKey ( string requestKey )
216
- {
217
- // "The value of this header field is constructed by concatenating /key/, defined above in step 4
218
- // in Section 4.2.2, with the string "258EAFA5- E914-47DA-95CA-C5AB0DC85B11", taking the SHA-1 hash of
219
- // this concatenated value to obtain a 20-byte value and base64-encoding"
220
- // https://tools.ietf.org/html/rfc6455#section-4.2.2
221
-
222
- if ( requestKey == null )
223
- {
224
- throw new ArgumentNullException ( nameof ( requestKey ) ) ;
225
- }
226
-
227
- using ( var algorithm = SHA1 . Create ( ) )
228
- {
229
- string merged = requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ;
230
- byte [ ] mergedBytes = Encoding . UTF8 . GetBytes ( merged ) ;
231
- byte [ ] hashedBytes = algorithm . ComputeHash ( mergedBytes ) ;
232
- return Convert . ToBase64String ( hashedBytes ) ;
233
- }
234
- }
235
-
236
- internal static class Constants
237
- {
238
- public static class Headers
239
- {
240
- public const string UpgradeWebSocket = "websocket" ;
241
- public const string ConnectionUpgrade = "Upgrade" ;
242
- public const string SupportedVersion = "13" ;
243
- }
244
- }
245
- }
246
46
}
247
47
}
0 commit comments