| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System; |
| | | 5 | | using System.Net; |
| | | 6 | | using System.Net.WebSockets; |
| | | 7 | | using System.Threading; |
| | | 8 | | using System.Threading.Tasks; |
| | | 9 | | using CoreWCF.Configuration; |
| | | 10 | | using CoreWCF.Runtime; |
| | | 11 | | using Microsoft.AspNetCore.Authentication; |
| | | 12 | | using Microsoft.AspNetCore.Builder; |
| | | 13 | | using Microsoft.AspNetCore.Http; |
| | | 14 | | using Microsoft.AspNetCore.Http.Features; |
| | | 15 | | using Microsoft.Extensions.DependencyInjection; |
| | | 16 | | using Microsoft.Extensions.Logging; |
| | | 17 | | |
| | | 18 | | namespace CoreWCF.Channels |
| | | 19 | | { |
| | | 20 | | internal class RequestDelegateHandler |
| | | 21 | | { |
| | | 22 | | internal const long DefaultMaxBufferPoolSize = 512 * 1024; |
| | | 23 | | private readonly IServiceDispatcher _serviceDispatcher; |
| | | 24 | | private readonly IDefaultCommunicationTimeouts _timeouts; |
| | | 25 | | private readonly IServiceScopeFactory _servicesScopeFactory; |
| | | 26 | | private HttpTransportSettings _httpSettings; |
| | | 27 | | private AspNetCoreReplyChannel _replyChannel; |
| | | 28 | | private Task<IServiceChannelDispatcher> _replyChannelDispatcherTask; |
| | | 29 | | private IServiceChannelDispatcher _replyChannelDispatcher; |
| | | 30 | | private bool _maxRequestBodySizeWarningEmitted = false; |
| | | 31 | | |
| | 423 | 32 | | public RequestDelegateHandler(IServiceDispatcher serviceDispatcher, IServiceScopeFactory servicesScopeFactory) |
| | | 33 | | { |
| | 423 | 34 | | _serviceDispatcher = serviceDispatcher; |
| | 423 | 35 | | _timeouts = _serviceDispatcher.Binding; |
| | 423 | 36 | | _servicesScopeFactory = servicesScopeFactory; |
| | 423 | 37 | | BuildHandler(); |
| | 423 | 38 | | } |
| | | 39 | | |
| | 1110 | 40 | | public bool IsAuthenticationRequired => _httpSettings.IsAuthenticationRequired; |
| | | 41 | | |
| | 1942 | 42 | | internal WebSocketOptions WebSocketOptions { get; set; } |
| | | 43 | | |
| | | 44 | | private void BuildHandler() |
| | | 45 | | { |
| | 423 | 46 | | BindingElementCollection be = _serviceDispatcher.Binding.CreateBindingElements(); |
| | 423 | 47 | | MessageEncodingBindingElement mebe = be.Find<MessageEncodingBindingElement>(); |
| | 423 | 48 | | if (mebe == null) |
| | | 49 | | { |
| | 0 | 50 | | throw new ArgumentException("Must provide a MessageEncodingBindingElement", nameof(_serviceDispatcher.Bi |
| | | 51 | | } |
| | | 52 | | |
| | 423 | 53 | | HttpTransportBindingElement tbe = be.Find<HttpTransportBindingElement>(); |
| | 423 | 54 | | if (tbe == null) |
| | | 55 | | { |
| | 0 | 56 | | throw new ArgumentException("Must provide a HttpTransportBindingElement", nameof(_serviceDispatcher.Bind |
| | | 57 | | } |
| | | 58 | | |
| | 423 | 59 | | var httpSettings = new HttpTransportSettings |
| | 423 | 60 | | { |
| | 423 | 61 | | BufferManager = BufferManager.CreateBufferManager(tbe.MaxBufferPoolSize, tbe.MaxBufferSize), |
| | 423 | 62 | | OpenTimeout = _serviceDispatcher.Binding.OpenTimeout, |
| | 423 | 63 | | ReceiveTimeout = _serviceDispatcher.Binding.ReceiveTimeout, |
| | 423 | 64 | | SendTimeout = _serviceDispatcher.Binding.SendTimeout, |
| | 423 | 65 | | CloseTimeout = _serviceDispatcher.Binding.CloseTimeout, |
| | 423 | 66 | | MaxBufferSize = tbe.MaxBufferSize, |
| | 423 | 67 | | MaxReceivedMessageSize = tbe.MaxReceivedMessageSize, |
| | 423 | 68 | | MessageEncoderFactory = mebe.CreateMessageEncoderFactory(), |
| | 423 | 69 | | ManualAddressing = tbe.ManualAddressing, |
| | 423 | 70 | | TransferMode = tbe.TransferMode, |
| | 423 | 71 | | KeepAliveEnabled = tbe.KeepAliveEnabled, |
| | 423 | 72 | | AnonymousUriPrefixMatcher = new HttpAnonymousUriPrefixMatcher(), |
| | 423 | 73 | | AuthenticationScheme = tbe.AuthenticationScheme, |
| | 423 | 74 | | WebSocketSettings = tbe.WebSocketSettings.Clone() |
| | 423 | 75 | | }; |
| | 423 | 76 | | _httpSettings = httpSettings; |
| | 423 | 77 | | WebSocketOptions = CreateWebSocketOptions(tbe); |
| | | 78 | | |
| | 423 | 79 | | if (WebSocketOptions == null || _serviceDispatcher.SupportedChannelTypes.Contains(typeof(IReplyChannel)) || |
| | | 80 | | { |
| | 422 | 81 | | _replyChannel = new AspNetCoreReplyChannel(_servicesScopeFactory.CreateScope().ServiceProvider, _httpSet |
| | 422 | 82 | | _replyChannelDispatcherTask = _serviceDispatcher.CreateServiceChannelDispatcherAsync(_replyChannel); |
| | | 83 | | } |
| | 423 | 84 | | } |
| | | 85 | | |
| | | 86 | | private WebSocketOptions CreateWebSocketOptions(HttpTransportBindingElement tbe) |
| | | 87 | | { |
| | | 88 | | // TODO: Is a check for IDuplexSessionChannel also needed? |
| | 423 | 89 | | bool canUseWebSockets = tbe.WebSocketSettings.TransportUsage == WebSocketTransportUsage.Always || |
| | 423 | 90 | | (tbe.WebSocketSettings.TransportUsage == WebSocketTransportUsage.WhenDuplex && _serviceDispatcher.Suppor |
| | 423 | 91 | | if (!canUseWebSockets) |
| | | 92 | | { |
| | 401 | 93 | | return null; |
| | | 94 | | } |
| | 22 | 95 | | return new WebSocketOptions |
| | 22 | 96 | | { |
| | 22 | 97 | | ReceiveBufferSize = WebSocketHelper.GetReceiveBufferSize(tbe.MaxReceivedMessageSize), |
| | 22 | 98 | | KeepAliveInterval = tbe.WebSocketSettings.GetEffectiveKeepAliveInterval() |
| | 22 | 99 | | }; |
| | | 100 | | } |
| | | 101 | | |
| | | 102 | | internal async Task HandleRequest(HttpContext context) |
| | | 103 | | { |
| | 687 | 104 | | EnsureMaxRequestBodySize(context); |
| | | 105 | | |
| | 687 | 106 | | if (IsAuthenticationRequired) |
| | | 107 | | { |
| | 81 | 108 | | string scheme = _httpSettings.AuthenticationScheme == AuthenticationSchemes.None |
| | 81 | 109 | | ? null |
| | 81 | 110 | | : _httpSettings.AuthenticationScheme.ToString(); |
| | | 111 | | |
| | 81 | 112 | | var authenticateResult = await context.AuthenticateAsync(scheme); |
| | | 113 | | |
| | 81 | 114 | | if (authenticateResult.None || !authenticateResult.Succeeded) |
| | | 115 | | { |
| | 25 | 116 | | await context.ChallengeAsync(scheme); |
| | 25 | 117 | | return; |
| | | 118 | | } |
| | | 119 | | |
| | 56 | 120 | | if (authenticateResult?.Principal != null) |
| | | 121 | | { |
| | 56 | 122 | | context.User = authenticateResult.Principal; |
| | | 123 | | } |
| | 56 | 124 | | } |
| | | 125 | | |
| | 662 | 126 | | if (!context.WebSockets.IsWebSocketRequest) |
| | | 127 | | { |
| | 651 | 128 | | if (WebSocketOptions != null && _replyChannelDispatcher == null && _replyChannelDispatcherTask == null) |
| | | 129 | | { |
| | 1 | 130 | | context.Response.StatusCode = (int)HttpStatusCode.BadRequest; |
| | 1 | 131 | | context.Features.Get<IHttpResponseFeature>().ReasonPhrase = SR.WebSocketEndpointOnlySupportWebSocket |
| | 1 | 132 | | return; |
| | | 133 | | } |
| | | 134 | | |
| | 650 | 135 | | if (_replyChannelDispatcher == null) |
| | | 136 | | { |
| | 314 | 137 | | _replyChannelDispatcher = await _replyChannelDispatcherTask; |
| | 314 | 138 | | _replyChannel.ChannelDispatcher = _replyChannelDispatcher; |
| | | 139 | | } |
| | | 140 | | |
| | 650 | 141 | | await _replyChannel.HandleRequest(context); |
| | | 142 | | } |
| | | 143 | | else |
| | | 144 | | { |
| | 11 | 145 | | CancellationToken openTimeoutToken = new TimeoutHelper(((IDefaultCommunicationTimeouts)_httpSettings).Op |
| | 11 | 146 | | WebSocketContext webSocketContext = await AcceptWebSocketAsync(context, openTimeoutToken); |
| | 11 | 147 | | if (webSocketContext == null) |
| | | 148 | | { |
| | 0 | 149 | | return; |
| | | 150 | | } |
| | | 151 | | |
| | 11 | 152 | | var channel = new ServerWebSocketTransportDuplexSessionChannel(context, webSocketContext, _httpSettings, |
| | 11 | 153 | | channel.ChannelDispatcher = await _serviceDispatcher.CreateServiceChannelDispatcherAsync(channel); |
| | 11 | 154 | | await channel.StartReceivingAsync(); |
| | | 155 | | |
| | | 156 | | // After the receive loop completes (client sent close frame), perform a graceful |
| | | 157 | | // WebSocket close handshake. Without this, on Linux/Kestrel the connection is aborted |
| | | 158 | | // when the handler returns, causing the client to see WebSocketException ('Aborted'). |
| | | 159 | | try |
| | | 160 | | { |
| | 11 | 161 | | CancellationToken closeTimeoutToken = new TimeoutHelper(((IDefaultCommunicationTimeouts)_httpSetting |
| | 11 | 162 | | await channel.CloseAsync(closeTimeoutToken); |
| | 11 | 163 | | } |
| | | 164 | | catch (Exception ex) |
| | | 165 | | { |
| | 0 | 166 | | if (Fx.IsFatal(ex)) |
| | | 167 | | { |
| | 0 | 168 | | throw; |
| | | 169 | | } |
| | | 170 | | |
| | | 171 | | // If the client has already disconnected or the close handshake fails for any reason, |
| | | 172 | | // abort the channel to clean up resources rather than letting the exception propagate. |
| | 0 | 173 | | DiagnosticUtility.TraceHandledException(ex, System.Diagnostics.TraceEventType.Warning); |
| | 0 | 174 | | channel.Abort(); |
| | 0 | 175 | | } |
| | 11 | 176 | | } |
| | 685 | 177 | | } |
| | | 178 | | |
| | | 179 | | private void EnsureMaxRequestBodySize(HttpContext context) |
| | | 180 | | { |
| | 687 | 181 | | if (_httpSettings is null) |
| | | 182 | | { |
| | 0 | 183 | | return; |
| | | 184 | | } |
| | | 185 | | |
| | 687 | 186 | | var maxRequestBodySizeFeature = context.Features.Get<IHttpMaxRequestBodySizeFeature>(); |
| | 687 | 187 | | if (maxRequestBodySizeFeature is null) |
| | | 188 | | { |
| | 40 | 189 | | return; |
| | | 190 | | } |
| | | 191 | | |
| | 647 | 192 | | long desiredMaxRequestBodySize = _httpSettings.MaxReceivedMessageSize; |
| | | 193 | | |
| | 647 | 194 | | if (maxRequestBodySizeFeature.MaxRequestBodySize != null) |
| | | 195 | | { |
| | 647 | 196 | | if (maxRequestBodySizeFeature.MaxRequestBodySize < desiredMaxRequestBodySize) |
| | | 197 | | { |
| | 18 | 198 | | if (!maxRequestBodySizeFeature.IsReadOnly) |
| | | 199 | | { |
| | 18 | 200 | | maxRequestBodySizeFeature.MaxRequestBodySize = desiredMaxRequestBodySize; |
| | | 201 | | } |
| | 0 | 202 | | else if (!_maxRequestBodySizeWarningEmitted) |
| | | 203 | | { |
| | 0 | 204 | | var logger = context.RequestServices.GetService<ILogger<RequestDelegateHandler>>(); |
| | 0 | 205 | | logger?.LogWarning(SR.MaxRequestBodySizeIsReadOnlyLogFormat, maxRequestBodySizeFeature.MaxReques |
| | 0 | 206 | | _maxRequestBodySizeWarningEmitted = true; |
| | | 207 | | } |
| | | 208 | | } |
| | | 209 | | } |
| | 629 | 210 | | } |
| | | 211 | | |
| | | 212 | | private async Task<WebSocketContext> AcceptWebSocketAsync(HttpContext context, CancellationToken token) |
| | | 213 | | { |
| | | 214 | | //if (TD.WebSocketConnectionAcceptStartIsEnabled()) |
| | | 215 | | //{ |
| | | 216 | | // TD.WebSocketConnectionAcceptStart(this.httpRequestContext.EventTraceActivity); |
| | | 217 | | //} |
| | | 218 | | |
| | 11 | 219 | | if (!context.WebSockets.IsWebSocketRequest) |
| | | 220 | | { |
| | 0 | 221 | | context.Response.StatusCode = (int)HttpStatusCode.BadRequest; |
| | 0 | 222 | | context.Features.Get<IHttpResponseFeature>().ReasonPhrase = SR.WebSocketEndpointOnlySupportWebSocketErro |
| | 0 | 223 | | return null; |
| | | 224 | | } |
| | | 225 | | |
| | | 226 | | try |
| | | 227 | | { |
| | 11 | 228 | | using (token.Register(() => { context.Abort(); })) |
| | | 229 | | { |
| | 11 | 230 | | string negotiatedProtocol = null; |
| | | 231 | | |
| | | 232 | | // match client protocols vs server protocol |
| | 11 | 233 | | if (context.WebSockets.WebSocketRequestedProtocols.Count != 0) |
| | | 234 | | { |
| | 30 | 235 | | foreach (string protocol in context.WebSockets.WebSocketRequestedProtocols) |
| | | 236 | | { |
| | 10 | 237 | | if (string.Compare(protocol, _httpSettings.WebSocketSettings.SubProtocol, |
| | 10 | 238 | | StringComparison.OrdinalIgnoreCase) == 0) |
| | | 239 | | { |
| | 10 | 240 | | negotiatedProtocol = protocol; |
| | 10 | 241 | | break; |
| | | 242 | | } |
| | | 243 | | } |
| | | 244 | | |
| | 10 | 245 | | if (negotiatedProtocol == null) |
| | | 246 | | { |
| | 0 | 247 | | string errorMessage = SR.Format(SR.WebSocketInvalidProtocolNotInClientList, |
| | 0 | 248 | | _httpSettings.WebSocketSettings.SubProtocol, |
| | 0 | 249 | | string.Join(", ", context.WebSockets.WebSocketRequestedProtocols)); |
| | 0 | 250 | | Fx.Exception.AsWarning(new WebException(errorMessage)); |
| | | 251 | | |
| | 0 | 252 | | context.Response.StatusCode = (int)HttpStatusCode.UpgradeRequired; |
| | 0 | 253 | | context.Features.Get<IHttpResponseFeature>().ReasonPhrase = |
| | 0 | 254 | | SR.WebSocketEndpointOnlySupportWebSocketError; |
| | 0 | 255 | | return null; |
| | | 256 | | } |
| | | 257 | | } |
| | 1 | 258 | | else if (!string.IsNullOrEmpty(_httpSettings.WebSocketSettings.SubProtocol)) |
| | | 259 | | { |
| | 0 | 260 | | context.Response.StatusCode = (int)HttpStatusCode.UpgradeRequired; |
| | 0 | 261 | | context.Features.Get<IHttpResponseFeature>().ReasonPhrase = |
| | 0 | 262 | | SR.WebSocketEndpointOnlySupportWebSocketError; |
| | 0 | 263 | | return null; |
| | | 264 | | } |
| | 11 | 265 | | WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(negotiatedProtocol); |
| | 11 | 266 | | return new AspNetCoreWebSocketContext(context, webSocket); |
| | | 267 | | } |
| | | 268 | | } |
| | | 269 | | catch (Exception ex) |
| | | 270 | | { |
| | 0 | 271 | | if (Fx.IsFatal(ex)) |
| | | 272 | | { |
| | 0 | 273 | | throw; |
| | | 274 | | } |
| | | 275 | | |
| | 0 | 276 | | if (token.IsCancellationRequested) |
| | | 277 | | { |
| | 0 | 278 | | throw Fx.Exception.AsError(new TimeoutException(SR.AcceptWebSocketTimedOutError)); |
| | | 279 | | } |
| | | 280 | | |
| | 0 | 281 | | WebSocketHelper.ThrowCorrectException(ex); |
| | 0 | 282 | | throw; |
| | | 283 | | } |
| | 11 | 284 | | } |
| | | 285 | | |
| | | 286 | | private void SendUpgradeRequiredResponseMessageWithSubProtocol() |
| | | 287 | | { |
| | 0 | 288 | | } |
| | | 289 | | |
| | | 290 | | internal async Task HandleDuplexConnection(HttpContext context) |
| | | 291 | | { |
| | 0 | 292 | | if (context.WebSockets.IsWebSocketRequest) |
| | | 293 | | { |
| | 0 | 294 | | WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(); |
| | | 295 | | } |
| | 0 | 296 | | } |
| | | 297 | | } |
| | | 298 | | } |