< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.Channels.RequestDelegateHandler
Assembly: CoreWCF.Http
File(s): /home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.Http/src/CoreWCF/Channels/RequestDelegateHandler.cs
Line coverage
72%
Covered lines: 101
Uncovered lines: 38
Coverable lines: 139
Total lines: 298
Line coverage: 72.6%
Branch coverage
72%
Covered branches: 54
Total branches: 74
Branch coverage: 72.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%11100%
BuildHandler()80%101093.33%
CreateWebSocketOptions(...)100%66100%
HandleRequest()84.61%262684.21%
EnsureMaxRequestBodySize(...)64.28%141466.66%
AcceptWebSocketAsync()56.25%161638.23%
SendUpgradeRequiredResponseMessageWithSubProtocol()100%110%
HandleDuplexConnection()0%220%

File(s)

/home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.Http/src/CoreWCF/Channels/RequestDelegateHandler.cs

#LineLine coverage
 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
 4using System;
 5using System.Net;
 6using System.Net.WebSockets;
 7using System.Threading;
 8using System.Threading.Tasks;
 9using CoreWCF.Configuration;
 10using CoreWCF.Runtime;
 11using Microsoft.AspNetCore.Authentication;
 12using Microsoft.AspNetCore.Builder;
 13using Microsoft.AspNetCore.Http;
 14using Microsoft.AspNetCore.Http.Features;
 15using Microsoft.Extensions.DependencyInjection;
 16using Microsoft.Extensions.Logging;
 17
 18namespace 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
 42332        public RequestDelegateHandler(IServiceDispatcher serviceDispatcher, IServiceScopeFactory servicesScopeFactory)
 33        {
 42334            _serviceDispatcher = serviceDispatcher;
 42335            _timeouts = _serviceDispatcher.Binding;
 42336            _servicesScopeFactory = servicesScopeFactory;
 42337            BuildHandler();
 42338        }
 39
 111040        public bool IsAuthenticationRequired => _httpSettings.IsAuthenticationRequired;
 41
 194242        internal WebSocketOptions WebSocketOptions { get; set; }
 43
 44        private void BuildHandler()
 45        {
 42346            BindingElementCollection be = _serviceDispatcher.Binding.CreateBindingElements();
 42347            MessageEncodingBindingElement mebe = be.Find<MessageEncodingBindingElement>();
 42348            if (mebe == null)
 49            {
 050                throw new ArgumentException("Must provide a MessageEncodingBindingElement", nameof(_serviceDispatcher.Bi
 51            }
 52
 42353            HttpTransportBindingElement tbe = be.Find<HttpTransportBindingElement>();
 42354            if (tbe == null)
 55            {
 056                throw new ArgumentException("Must provide a HttpTransportBindingElement", nameof(_serviceDispatcher.Bind
 57            }
 58
 42359            var httpSettings = new HttpTransportSettings
 42360            {
 42361                BufferManager = BufferManager.CreateBufferManager(tbe.MaxBufferPoolSize, tbe.MaxBufferSize),
 42362                OpenTimeout = _serviceDispatcher.Binding.OpenTimeout,
 42363                ReceiveTimeout = _serviceDispatcher.Binding.ReceiveTimeout,
 42364                SendTimeout = _serviceDispatcher.Binding.SendTimeout,
 42365                CloseTimeout = _serviceDispatcher.Binding.CloseTimeout,
 42366                MaxBufferSize = tbe.MaxBufferSize,
 42367                MaxReceivedMessageSize = tbe.MaxReceivedMessageSize,
 42368                MessageEncoderFactory = mebe.CreateMessageEncoderFactory(),
 42369                ManualAddressing = tbe.ManualAddressing,
 42370                TransferMode = tbe.TransferMode,
 42371                KeepAliveEnabled = tbe.KeepAliveEnabled,
 42372                AnonymousUriPrefixMatcher = new HttpAnonymousUriPrefixMatcher(),
 42373                AuthenticationScheme = tbe.AuthenticationScheme,
 42374                WebSocketSettings = tbe.WebSocketSettings.Clone()
 42375            };
 42376            _httpSettings = httpSettings;
 42377            WebSocketOptions = CreateWebSocketOptions(tbe);
 78
 42379            if (WebSocketOptions == null || _serviceDispatcher.SupportedChannelTypes.Contains(typeof(IReplyChannel)) || 
 80            {
 42281                _replyChannel = new AspNetCoreReplyChannel(_servicesScopeFactory.CreateScope().ServiceProvider, _httpSet
 42282                _replyChannelDispatcherTask = _serviceDispatcher.CreateServiceChannelDispatcherAsync(_replyChannel);
 83            }
 42384        }
 85
 86        private WebSocketOptions CreateWebSocketOptions(HttpTransportBindingElement tbe)
 87        {
 88            // TODO: Is a check for IDuplexSessionChannel also needed?
 42389            bool canUseWebSockets = tbe.WebSocketSettings.TransportUsage == WebSocketTransportUsage.Always ||
 42390                (tbe.WebSocketSettings.TransportUsage == WebSocketTransportUsage.WhenDuplex && _serviceDispatcher.Suppor
 42391            if (!canUseWebSockets)
 92            {
 40193                return null;
 94            }
 2295            return new WebSocketOptions
 2296            {
 2297                ReceiveBufferSize = WebSocketHelper.GetReceiveBufferSize(tbe.MaxReceivedMessageSize),
 2298                KeepAliveInterval = tbe.WebSocketSettings.GetEffectiveKeepAliveInterval()
 2299            };
 100        }
 101
 102        internal async Task HandleRequest(HttpContext context)
 103        {
 687104            EnsureMaxRequestBodySize(context);
 105
 687106            if (IsAuthenticationRequired)
 107            {
 81108                string scheme = _httpSettings.AuthenticationScheme == AuthenticationSchemes.None
 81109                    ? null
 81110                    : _httpSettings.AuthenticationScheme.ToString();
 111
 81112                var authenticateResult = await context.AuthenticateAsync(scheme);
 113
 81114                if (authenticateResult.None || !authenticateResult.Succeeded)
 115                {
 25116                    await context.ChallengeAsync(scheme);
 25117                    return;
 118                }
 119
 56120                if (authenticateResult?.Principal != null)
 121                {
 56122                    context.User = authenticateResult.Principal;
 123                }
 56124            }
 125
 662126            if (!context.WebSockets.IsWebSocketRequest)
 127            {
 651128                if (WebSocketOptions != null && _replyChannelDispatcher == null && _replyChannelDispatcherTask == null)
 129                {
 1130                    context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
 1131                    context.Features.Get<IHttpResponseFeature>().ReasonPhrase = SR.WebSocketEndpointOnlySupportWebSocket
 1132                    return;
 133                }
 134
 650135                if (_replyChannelDispatcher == null)
 136                {
 314137                    _replyChannelDispatcher = await _replyChannelDispatcherTask;
 314138                    _replyChannel.ChannelDispatcher = _replyChannelDispatcher;
 139                }
 140
 650141                await _replyChannel.HandleRequest(context);
 142            }
 143            else
 144            {
 11145                CancellationToken openTimeoutToken = new TimeoutHelper(((IDefaultCommunicationTimeouts)_httpSettings).Op
 11146                WebSocketContext webSocketContext = await AcceptWebSocketAsync(context, openTimeoutToken);
 11147                if (webSocketContext == null)
 148                {
 0149                    return;
 150                }
 151
 11152                var channel = new ServerWebSocketTransportDuplexSessionChannel(context, webSocketContext, _httpSettings,
 11153                channel.ChannelDispatcher = await _serviceDispatcher.CreateServiceChannelDispatcherAsync(channel);
 11154                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                {
 11161                    CancellationToken closeTimeoutToken = new TimeoutHelper(((IDefaultCommunicationTimeouts)_httpSetting
 11162                    await channel.CloseAsync(closeTimeoutToken);
 11163                }
 164                catch (Exception ex)
 165                {
 0166                    if (Fx.IsFatal(ex))
 167                    {
 0168                        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.
 0173                    DiagnosticUtility.TraceHandledException(ex, System.Diagnostics.TraceEventType.Warning);
 0174                    channel.Abort();
 0175                }
 11176            }
 685177        }
 178
 179        private void EnsureMaxRequestBodySize(HttpContext context)
 180        {
 687181            if (_httpSettings is null)
 182            {
 0183                return;
 184            }
 185
 687186            var maxRequestBodySizeFeature = context.Features.Get<IHttpMaxRequestBodySizeFeature>();
 687187            if (maxRequestBodySizeFeature is null)
 188            {
 40189                return;
 190            }
 191
 647192            long desiredMaxRequestBodySize = _httpSettings.MaxReceivedMessageSize;
 193
 647194            if (maxRequestBodySizeFeature.MaxRequestBodySize != null)
 195            {
 647196                if (maxRequestBodySizeFeature.MaxRequestBodySize < desiredMaxRequestBodySize)
 197                {
 18198                    if (!maxRequestBodySizeFeature.IsReadOnly)
 199                    {
 18200                        maxRequestBodySizeFeature.MaxRequestBodySize = desiredMaxRequestBodySize;
 201                    }
 0202                    else if (!_maxRequestBodySizeWarningEmitted)
 203                    {
 0204                        var logger = context.RequestServices.GetService<ILogger<RequestDelegateHandler>>();
 0205                        logger?.LogWarning(SR.MaxRequestBodySizeIsReadOnlyLogFormat, maxRequestBodySizeFeature.MaxReques
 0206                        _maxRequestBodySizeWarningEmitted = true;
 207                    }
 208                }
 209            }
 629210        }
 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
 11219            if (!context.WebSockets.IsWebSocketRequest)
 220            {
 0221                context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
 0222                context.Features.Get<IHttpResponseFeature>().ReasonPhrase = SR.WebSocketEndpointOnlySupportWebSocketErro
 0223                return null;
 224            }
 225
 226            try
 227            {
 11228                using (token.Register(() => { context.Abort(); }))
 229                {
 11230                    string negotiatedProtocol = null;
 231
 232                    // match client protocols vs server protocol
 11233                    if (context.WebSockets.WebSocketRequestedProtocols.Count != 0)
 234                    {
 30235                        foreach (string protocol in context.WebSockets.WebSocketRequestedProtocols)
 236                        {
 10237                            if (string.Compare(protocol, _httpSettings.WebSocketSettings.SubProtocol,
 10238                                    StringComparison.OrdinalIgnoreCase) == 0)
 239                            {
 10240                                negotiatedProtocol = protocol;
 10241                                break;
 242                            }
 243                        }
 244
 10245                        if (negotiatedProtocol == null)
 246                        {
 0247                            string errorMessage = SR.Format(SR.WebSocketInvalidProtocolNotInClientList,
 0248                                _httpSettings.WebSocketSettings.SubProtocol,
 0249                                string.Join(", ", context.WebSockets.WebSocketRequestedProtocols));
 0250                            Fx.Exception.AsWarning(new WebException(errorMessage));
 251
 0252                            context.Response.StatusCode = (int)HttpStatusCode.UpgradeRequired;
 0253                            context.Features.Get<IHttpResponseFeature>().ReasonPhrase =
 0254                                SR.WebSocketEndpointOnlySupportWebSocketError;
 0255                            return null;
 256                        }
 257                    }
 1258                    else if (!string.IsNullOrEmpty(_httpSettings.WebSocketSettings.SubProtocol))
 259                    {
 0260                        context.Response.StatusCode = (int)HttpStatusCode.UpgradeRequired;
 0261                        context.Features.Get<IHttpResponseFeature>().ReasonPhrase =
 0262                            SR.WebSocketEndpointOnlySupportWebSocketError;
 0263                        return null;
 264                    }
 11265                    WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(negotiatedProtocol);
 11266                    return new AspNetCoreWebSocketContext(context, webSocket);
 267                }
 268            }
 269            catch (Exception ex)
 270            {
 0271                if (Fx.IsFatal(ex))
 272                {
 0273                    throw;
 274                }
 275
 0276                if (token.IsCancellationRequested)
 277                {
 0278                    throw Fx.Exception.AsError(new TimeoutException(SR.AcceptWebSocketTimedOutError));
 279                }
 280
 0281                WebSocketHelper.ThrowCorrectException(ex);
 0282                throw;
 283            }
 11284        }
 285
 286        private void SendUpgradeRequiredResponseMessageWithSubProtocol()
 287        {
 0288        }
 289
 290        internal async Task HandleDuplexConnection(HttpContext context)
 291        {
 0292            if (context.WebSockets.IsWebSocketRequest)
 293            {
 0294                WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
 295            }
 0296        }
 297    }
 298}