< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.Dispatcher.WebHttpDispatchOperationSelector
Assembly: CoreWCF.WebHttp
File(s): /home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.WebHttp/src/CoreWCF/Dispatcher/WebHttpDispatchOperationSelector.cs
Line coverage
47%
Covered lines: 71
Uncovered lines: 78
Coverable lines: 149
Total lines: 386
Line coverage: 47.6%
Branch coverage
40%
Covered branches: 40
Total branches: 98
Branch coverage: 40.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)78.57%282871.11%
.ctor()100%110%
GetUriTemplate(...)0%440%
SelectOperation(...)75%4485.71%
SelectOperation(...)32.5%404037.5%
CanUriMatch(...)100%22100%
AddUriTemplateMatch(...)100%11100%
ShouldRedirectToUriWithSlashAtTheEnd(...)0%14140%
GetAuthority(...)0%220%
.ctor(...)100%11100%
Equals(...)0%440%
GetHashCode()100%11100%

File(s)

/home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.WebHttp/src/CoreWCF/Dispatcher/WebHttpDispatchOperationSelector.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.Collections.Generic;
 6using System.Net;
 7using CoreWCF.Channels;
 8using CoreWCF.Collections.Generic;
 9using CoreWCF.Description;
 10using CoreWCF.Runtime;
 11using CoreWCF.Web;
 12
 13namespace CoreWCF.Dispatcher
 14{
 15    public class WebHttpDispatchOperationSelector : IDispatchOperationSelector
 16    {
 17        public const string HttpOperationSelectorUriMatchedPropertyName = "UriMatched";
 18        internal const string HttpOperationSelectorDataPropertyName = "HttpOperationSelectorData";
 19
 20        public const string HttpOperationNamePropertyName = "HttpOperationName";
 21        internal const string RedirectOperationName = ""; // always unhandled invoker
 22        internal const string RedirectPropertyName = "WebHttpRedirect";
 23
 3724        private readonly string _catchAllOperationName = ""; // user UT=* Method=* operation, else unhandled invoker
 25
 26        private readonly Dictionary<string, UriTemplateTable> _methodSpecificTables; // indexed by the http method name
 27        private readonly UriTemplateTable _wildcardTable; // this is one of the methodSpecificTables, special-cased for 
 28        private readonly Dictionary<string, UriTemplate> _templates;
 29        private readonly UriTemplateTable _helpUriTable;
 30
 3731        public WebHttpDispatchOperationSelector(ServiceEndpoint endpoint)
 32        {
 3733            if (endpoint == null)
 34            {
 035                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(endpoint));
 36            }
 37
 3738            if (endpoint.Address == null)
 39            {
 040                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
 041                    SR.EndpointAddressCannotBeNull));
 42            }
 43
 3744            Uri baseUri = endpoint.Address.Uri;
 3745            _methodSpecificTables = new Dictionary<string, UriTemplateTable>();
 3746            _templates = new Dictionary<string, UriTemplate>();
 47
 3748            WebHttpBehavior webHttpBehavior = ((KeyedByTypeCollection<IEndpointBehavior>)endpoint.EndpointBehaviors).Fin
 3749            if (webHttpBehavior != null && webHttpBehavior.HelpEnabled)
 50            {
 051                _helpUriTable = new UriTemplateTable(endpoint.ListenUri, HelpPage.GetOperationTemplatePairs());
 52            }
 53
 3754            Dictionary<WCFKey, string> alreadyHaves = new Dictionary<WCFKey, string>();
 55
 40056            foreach (OperationDescription od in endpoint.Contract.Operations)
 57            {
 58                // ignore callback operations
 16359                if (od.Messages[0].Direction == MessageDirection.Input)
 60                {
 16361                    string method = WebHttpBehavior.GetWebMethod(od);
 16362                    string path = UriTemplateClientFormatter.GetUTStringOrDefault(od);
 63
 64                    //
 65
 16366                    if (UriTemplateHelpers.IsWildcardPath(path) && (method == WebHttpBehavior.WildcardMethod))
 67                    {
 868                        if (_catchAllOperationName != "")
 69                        {
 070                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
 071                                new InvalidOperationException(
 072                                SR.Format(SR.MultipleOperationsInContractWithPathMethod,
 073                                endpoint.Contract.Name, path, method)));
 74                        }
 875                        _catchAllOperationName = od.Name;
 76                    }
 77
 16378                    UriTemplate ut = new UriTemplate(path);
 16379                    WCFKey wcfKey = new WCFKey(ut, method);
 16380                    if (alreadyHaves.ContainsKey(wcfKey))
 81                    {
 082                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
 083                            new InvalidOperationException(
 084                            SR.Format(SR.MultipleOperationsInContractWithPathMethod,
 085                            endpoint.Contract.Name, path, method)));
 86                    }
 87
 16388                    alreadyHaves.Add(wcfKey, od.Name);
 89
 16390                    if (!_methodSpecificTables.TryGetValue(method, out UriTemplateTable methodSpecificTable))
 91                    {
 4792                        methodSpecificTable = new UriTemplateTable(baseUri);
 4793                        _methodSpecificTables.Add(method, methodSpecificTable);
 94                    }
 95
 16396                    methodSpecificTable.KeyValuePairs.Add(new KeyValuePair<UriTemplate, object>(ut, od.Name));
 16397                    _templates.Add(od.Name, ut);
 98                }
 99            }
 100
 37101            if (_methodSpecificTables.Count == 0)
 102            {
 0103                _methodSpecificTables = null;
 104            }
 105            else
 106            {
 107                // freeze all the tables because they should not be modified after this point
 168108                foreach (UriTemplateTable table in _methodSpecificTables.Values)
 109                {
 47110                    table.MakeReadOnly(true /* allowDuplicateEquivalentUriTemplates */);
 111                }
 112
 37113                if (!_methodSpecificTables.TryGetValue(WebHttpBehavior.WildcardMethod, out _wildcardTable))
 114                {
 29115                    _wildcardTable = null;
 116                }
 117            }
 37118        }
 119
 0120        protected WebHttpDispatchOperationSelector()
 121        {
 0122        }
 123
 124        public virtual UriTemplate GetUriTemplate(string operationName)
 125        {
 0126            if (operationName == null)
 127            {
 0128                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(operationName));
 129            }
 130
 0131            if (!_templates.TryGetValue(operationName, out UriTemplate result))
 132            {
 0133                return null;
 134            }
 135            else
 136            {
 0137                return result;
 138            }
 139        }
 140
 141        public string SelectOperation(ref Message message)
 142        {
 35143            if (message == null)
 144            {
 0145                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(message));
 146            }
 147
 35148            string result = SelectOperation(ref message, out bool uriMatched);
 35149            message.Properties.Add(HttpOperationSelectorUriMatchedPropertyName, uriMatched);
 35150            if (result != null)
 151            {
 35152                message.Properties.Add(HttpOperationNamePropertyName, result);
 153//                if (DiagnosticUtility.ShouldTraceInformation)
 154//                {
 155//#pragma warning disable 56506 // Microsoft, Message.Headers is never null
 156//                    TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.WebRequestMatchesOperation, SR2.GetS
 157//#pragma warning restore 56506
 158//                }
 159            }
 160
 35161            return result;
 162        }
 163
 164        protected virtual string SelectOperation(ref Message message, out bool uriMatched)
 165        {
 35166            if (message == null)
 167            {
 0168                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(message));
 169            }
 170
 35171            uriMatched = false;
 35172            if (_methodSpecificTables == null)
 173            {
 0174                return _catchAllOperationName;
 175            }
 176
 35177            if (!message.Properties.ContainsKey(HttpRequestMessageProperty.Name))
 178            {
 0179                return _catchAllOperationName;
 180            }
 181
 35182            if (!(message.Properties[HttpRequestMessageProperty.Name] is HttpRequestMessageProperty prop))
 183            {
 0184                return _catchAllOperationName;
 185            }
 186
 35187            string method = prop.Method;
 188
 35189            Uri to = message.Headers.To;
 190
 35191            if (to == null)
 192            {
 0193                return _catchAllOperationName;
 194            }
 195
 35196            if (_helpUriTable != null)
 197            {
 0198                UriTemplateMatch match = _helpUriTable.MatchSingle(to);
 0199                if (match != null)
 200                {
 0201                    uriMatched = true;
 0202                    AddUriTemplateMatch(match, prop, message);
 0203                    if (method == WebHttpBehavior.GET)
 204                    {
 0205                        return HelpOperationInvoker.OperationName;
 206                    }
 0207                    message.Properties.Add(HttpOperationSelectorDataPropertyName,
 0208                        new WebHttpDispatchOperationSelectorData() { AllowedMethods = new List<string>() { WebHttpBehavi
 0209                    return _catchAllOperationName;
 210                }
 211            }
 212
 35213            bool methodMatchesExactly = _methodSpecificTables.TryGetValue(method, out UriTemplateTable methodSpecificTab
 35214            if (methodMatchesExactly)
 215            {
 35216                uriMatched = CanUriMatch(methodSpecificTable, to, prop, message, out string operationName);
 35217                if (uriMatched)
 218                {
 34219                    return operationName;
 220                }
 221            }
 222
 1223            if (_wildcardTable != null)
 224            {
 1225                uriMatched = CanUriMatch(_wildcardTable, to, prop, message, out string operationName);
 1226                if (uriMatched)
 227                {
 1228                    return operationName;
 229                }
 230            }
 231
 0232            if (ShouldRedirectToUriWithSlashAtTheEnd(methodSpecificTable, message, to))
 233            {
 0234                return RedirectOperationName;
 235            }
 236
 237            // the {method, uri} pair does not match anything the service supports.
 238            // we know at this point that we'll return some kind of error code, but we
 239            // should go through all methods for the uri to see if any method is supported
 240            // so that that information could be returned to the user as well
 241
 0242            List<string> allowedMethods = null;
 0243            foreach (KeyValuePair<string, UriTemplateTable> pair in _methodSpecificTables)
 244            {
 0245                if (pair.Key == method || pair.Key == WebHttpBehavior.WildcardMethod)
 246                {
 247                    // the uri must not match the uri template
 248                    continue;
 249                }
 250
 0251                UriTemplateTable table = pair.Value;
 0252                if (table.MatchSingle(to) != null)
 253                {
 0254                    if (allowedMethods == null)
 255                    {
 0256                        allowedMethods = new List<string>();
 257                    }
 258
 259                    //
 260
 0261                    if (!allowedMethods.Contains(pair.Key))
 262                    {
 0263                        allowedMethods.Add(pair.Key);
 264                    }
 265                }
 266            }
 267
 0268            if (allowedMethods != null)
 269            {
 0270                uriMatched = true;
 0271                message.Properties.Add(HttpOperationSelectorDataPropertyName,
 0272                    new WebHttpDispatchOperationSelectorData() { AllowedMethods = allowedMethods });
 273            }
 274
 0275            return _catchAllOperationName;
 276        }
 277
 278        private bool CanUriMatch(UriTemplateTable methodSpecificTable, Uri to, HttpRequestMessageProperty prop, Message 
 279        {
 36280            operationName = null;
 36281            UriTemplateMatch result = methodSpecificTable.MatchSingle(to);
 282
 36283            if (result != null)
 284            {
 35285                operationName = result.Data as string;
 286                Fx.Assert(operationName != null, "bad result");
 35287                AddUriTemplateMatch(result, prop, message);
 35288                return true;
 289            }
 290
 1291            return false;
 292        }
 293
 294        private void AddUriTemplateMatch(UriTemplateMatch match, HttpRequestMessageProperty requestProp, Message message
 295        {
 35296            match.SetBaseUri(match.BaseUri, requestProp);
 35297            message.Properties.Add(IncomingWebRequestContext.UriTemplateMatchResultsPropertyName, match);
 35298        }
 299
 300        private bool ShouldRedirectToUriWithSlashAtTheEnd(UriTemplateTable methodSpecificTable, Message message, Uri to)
 301        {
 0302            UriBuilder ub = new UriBuilder(to);
 0303            if (ub.Path.EndsWith("/", StringComparison.Ordinal))
 304            {
 0305                return false;
 306            }
 307
 0308            ub.Path = ub.Path + "/";
 0309            Uri originalPlusSlash = ub.Uri;
 310
 0311            bool result = false;
 0312            if (methodSpecificTable != null && methodSpecificTable.MatchSingle(originalPlusSlash) != null)
 313            {
 314                // as an optimization, we check the table that matched the request's method
 315                // first, as it is more probable that a hit happens there
 0316                result = true;
 317            }
 318            else
 319            {
 320                // back-compat:
 321                // we will redirect as long as there is any method
 322                // - not necessary the one the user is looking for -
 323                // that matches the uri with a slash at the end
 324
 0325                foreach (KeyValuePair<string, UriTemplateTable> pair in _methodSpecificTables)
 326                {
 0327                    UriTemplateTable table = pair.Value;
 0328                    if (table != methodSpecificTable && table.MatchSingle(originalPlusSlash) != null)
 329                    {
 0330                        result = true;
 0331                        break;
 332                    }
 333                }
 334            }
 335
 0336            if (result)
 337            {
 0338                string hostAndPort = GetAuthority(message);
 0339                originalPlusSlash = UriTemplate.RewriteUri(ub.Uri, hostAndPort);
 0340                message.Properties.Add(RedirectPropertyName, originalPlusSlash);
 341            }
 342
 0343            return result;
 344        }
 345
 346        private static string GetAuthority(Message message)
 347        {
 0348            string hostName = null;
 0349            if (message.Properties.TryGetValue(HttpRequestMessageProperty.Name, out HttpRequestMessageProperty requestPr
 350            {
 0351                hostName = requestProperty.Headers[HttpRequestHeader.Host];
 0352                if (!string.IsNullOrEmpty(hostName))
 353                {
 0354                    return hostName;
 355                }
 356            }
 357
 0358            return hostName;
 359        }
 360
 361        // to enforce that no two ops have same UriTemplate & Method
 362        internal class WCFKey
 363        {
 364            private readonly string _method;
 365            private readonly UriTemplate _uriTemplate;
 366
 163367            public WCFKey(UriTemplate uriTemplate, string method)
 368            {
 163369                _uriTemplate = uriTemplate;
 163370                _method = method;
 163371            }
 372
 373            public override bool Equals(object obj)
 374            {
 0375                if (!(obj is WCFKey other))
 376                {
 0377                    return false;
 378                }
 379
 0380                return _uriTemplate.IsEquivalentTo(other._uriTemplate) && _method == other._method;
 381            }
 382
 289383            public override int GetHashCode() => UriTemplateEquivalenceComparer.Instance.GetHashCode(_uriTemplate);
 384        }
 385    }
 386}