Use ReferenceEquals in HttpMethodMatcherPolicy (#21277)
This commit is contained in:
parent
21e1020b88
commit
5cfebf260f
|
|
@ -248,6 +248,8 @@ namespace Microsoft.AspNetCore.Http
|
|||
public static readonly string Post;
|
||||
public static readonly string Put;
|
||||
public static readonly string Trace;
|
||||
public static bool Equals(string methodA, string methodB) { throw null; }
|
||||
public static string GetCanonicalizedValue(string method) { throw null; }
|
||||
public static bool IsConnect(string method) { throw null; }
|
||||
public static bool IsDelete(string method) { throw null; }
|
||||
public static bool IsGet(string method) { throw null; }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Http
|
|||
// Using .'static readonly' means that all consumers get these exact same
|
||||
// 'string' instance, which means the 'ReferenceEquals' checks below work
|
||||
// and allow us to optimize comparisons when these constants are used.
|
||||
|
||||
|
||||
// Please do NOT change these to 'const'
|
||||
public static readonly string Connect = "CONNECT";
|
||||
public static readonly string Delete = "DELETE";
|
||||
|
|
@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Http
|
|||
/// </returns>
|
||||
public static bool IsConnect(string method)
|
||||
{
|
||||
return object.ReferenceEquals(Connect, method) || StringComparer.OrdinalIgnoreCase.Equals(Connect, method);
|
||||
return Equals(Connect, method);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Http
|
|||
/// </returns>
|
||||
public static bool IsDelete(string method)
|
||||
{
|
||||
return object.ReferenceEquals(Delete, method) || StringComparer.OrdinalIgnoreCase.Equals(Delete, method);
|
||||
return Equals(Delete, method);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -57,11 +57,11 @@ namespace Microsoft.AspNetCore.Http
|
|||
/// </summary>
|
||||
/// <param name="method">The HTTP request method.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if the method is GET; otherwise, <see langword="false" />.
|
||||
/// <see langword="true" /> if the method is GET; otherwise, <see langword="false" />.
|
||||
/// </returns>
|
||||
public static bool IsGet(string method)
|
||||
{
|
||||
return object.ReferenceEquals(Get, method) || StringComparer.OrdinalIgnoreCase.Equals(Get, method);
|
||||
return Equals(Get, method);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -69,11 +69,11 @@ namespace Microsoft.AspNetCore.Http
|
|||
/// </summary>
|
||||
/// <param name="method">The HTTP request method.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if the method is HEAD; otherwise, <see langword="false" />.
|
||||
/// <see langword="true" /> if the method is HEAD; otherwise, <see langword="false" />.
|
||||
/// </returns>
|
||||
public static bool IsHead(string method)
|
||||
{
|
||||
return object.ReferenceEquals(Head, method) || StringComparer.OrdinalIgnoreCase.Equals(Head, method);
|
||||
return Equals(Head, method);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -81,11 +81,11 @@ namespace Microsoft.AspNetCore.Http
|
|||
/// </summary>
|
||||
/// <param name="method">The HTTP request method.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if the method is OPTIONS; otherwise, <see langword="false" />.
|
||||
/// <see langword="true" /> if the method is OPTIONS; otherwise, <see langword="false" />.
|
||||
/// </returns>
|
||||
public static bool IsOptions(string method)
|
||||
{
|
||||
return object.ReferenceEquals(Options, method) || StringComparer.OrdinalIgnoreCase.Equals(Options, method);
|
||||
return Equals(Options, method);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -97,7 +97,7 @@ namespace Microsoft.AspNetCore.Http
|
|||
/// </returns>
|
||||
public static bool IsPatch(string method)
|
||||
{
|
||||
return object.ReferenceEquals(Patch, method) || StringComparer.OrdinalIgnoreCase.Equals(Patch, method);
|
||||
return Equals(Patch, method);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -109,7 +109,7 @@ namespace Microsoft.AspNetCore.Http
|
|||
/// </returns>
|
||||
public static bool IsPost(string method)
|
||||
{
|
||||
return object.ReferenceEquals(Post, method) || StringComparer.OrdinalIgnoreCase.Equals(Post, method);
|
||||
return Equals(Post, method);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -121,7 +121,7 @@ namespace Microsoft.AspNetCore.Http
|
|||
/// </returns>
|
||||
public static bool IsPut(string method)
|
||||
{
|
||||
return object.ReferenceEquals(Put, method) || StringComparer.OrdinalIgnoreCase.Equals(Put, method);
|
||||
return Equals(Put, method);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -133,7 +133,39 @@ namespace Microsoft.AspNetCore.Http
|
|||
/// </returns>
|
||||
public static bool IsTrace(string method)
|
||||
{
|
||||
return object.ReferenceEquals(Trace, method) || StringComparer.OrdinalIgnoreCase.Equals(Trace, method);
|
||||
return Equals(Trace, method);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the equivalent static instance, or the original instance if none match. This conversion is optional but allows for performance optimizations when comparing method values elsewhere.
|
||||
/// </summary>
|
||||
/// <param name="method"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetCanonicalizedValue(string method) => method switch
|
||||
{
|
||||
string _ when IsGet(method) => Get,
|
||||
string _ when IsPost(method) => Post,
|
||||
string _ when IsPut(method) => Put,
|
||||
string _ when IsDelete(method) => Delete,
|
||||
string _ when IsOptions(method) => Options,
|
||||
string _ when IsHead(method) => Head,
|
||||
string _ when IsPatch(method) => Patch,
|
||||
string _ when IsTrace(method) => Trace,
|
||||
string _ when IsConnect(method) => Connect,
|
||||
string _ => method
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value that indicates if the HTTP methods are the same.
|
||||
/// </summary>
|
||||
/// <param name="methodA">The first HTTP request method to compare.</param>
|
||||
/// <param name="methodB">The second HTTP request method to compare.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if the methods are the same; otherwise, <see langword="false" />.
|
||||
/// </returns>
|
||||
public static bool Equals(string methodA, string methodB)
|
||||
{
|
||||
return object.ReferenceEquals(methodA, methodB) || StringComparer.OrdinalIgnoreCase.Equals(methodA, methodB);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http.Abstractions
|
||||
{
|
||||
public class HttpMethodslTests
|
||||
{
|
||||
[Fact]
|
||||
public void CanonicalizedValue_Success()
|
||||
{
|
||||
var testCases = new List<(string[] methods, string expectedMethod)>
|
||||
{
|
||||
(new string[] { "GET", "Get", "get" }, HttpMethods.Get),
|
||||
(new string[] { "POST", "Post", "post" }, HttpMethods.Post),
|
||||
(new string[] { "PUT", "Put", "put" }, HttpMethods.Put),
|
||||
(new string[] { "DELETE", "Delete", "delete" }, HttpMethods.Delete),
|
||||
(new string[] { "HEAD", "Head", "head" }, HttpMethods.Head),
|
||||
(new string[] { "CONNECT", "Connect", "connect" }, HttpMethods.Connect),
|
||||
(new string[] { "OPTIONS", "Options", "options" }, HttpMethods.Options),
|
||||
(new string[] { "PATCH", "Patch", "patch" }, HttpMethods.Patch),
|
||||
(new string[] { "TRACE", "Trace", "trace" }, HttpMethods.Trace)
|
||||
};
|
||||
|
||||
for (int i = 0; i < testCases.Count; i++)
|
||||
{
|
||||
var testCase = testCases[i];
|
||||
for (int j = 0; j < testCase.methods.Length; j++)
|
||||
{
|
||||
CanonicalizedValueTest(testCase.methods[j], testCase.expectedMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void CanonicalizedValueTest(string method, string expectedMethod)
|
||||
{
|
||||
string inputMethod = CreateStringAtRuntime(method);
|
||||
var canonicalizedValue = HttpMethods.GetCanonicalizedValue(inputMethod);
|
||||
|
||||
Assert.Same(expectedMethod, canonicalizedValue);
|
||||
}
|
||||
|
||||
private string CreateStringAtRuntime(string input)
|
||||
{
|
||||
return new StringBuilder(input).ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using static Microsoft.AspNetCore.Http.HttpMethods;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
|
|
@ -41,7 +42,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
throw new ArgumentNullException(nameof(httpMethods));
|
||||
}
|
||||
|
||||
HttpMethods = httpMethods.ToArray();
|
||||
HttpMethods = httpMethods.Select(GetCanonicalizedValue).ToArray();
|
||||
AcceptCorsPreflight = acceptCorsPreflight;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
|
@ -21,7 +22,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
// Used in tests
|
||||
internal static readonly string OriginHeader = "Origin";
|
||||
internal static readonly string AccessControlRequestMethod = "Access-Control-Request-Method";
|
||||
internal static readonly string PreflightHttpMethod = "OPTIONS";
|
||||
internal static readonly string PreflightHttpMethod = HttpMethods.Options;
|
||||
|
||||
// Used in tests
|
||||
internal const string Http405EndpointDisplayName = "405 HTTP Method Not Supported";
|
||||
|
|
@ -133,7 +134,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var httpMethod = httpContext.Request.Method;
|
||||
var headers = httpContext.Request.Headers;
|
||||
if (metadata.AcceptCorsPreflight &&
|
||||
string.Equals(httpMethod, PreflightHttpMethod, StringComparison.OrdinalIgnoreCase) &&
|
||||
HttpMethods.Equals(httpMethod, PreflightHttpMethod) &&
|
||||
headers.ContainsKey(HeaderNames.Origin) &&
|
||||
headers.TryGetValue(HeaderNames.AccessControlRequestMethod, out var accessControlRequestMethod) &&
|
||||
!StringValues.IsNullOrEmpty(accessControlRequestMethod))
|
||||
|
|
@ -146,7 +147,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
for (var j = 0; j < metadata.HttpMethods.Count; j++)
|
||||
{
|
||||
var candidateMethod = metadata.HttpMethods[j];
|
||||
if (!string.Equals(httpMethod, candidateMethod, StringComparison.OrdinalIgnoreCase))
|
||||
if (!HttpMethods.Equals(httpMethod, candidateMethod))
|
||||
{
|
||||
methods = methods ?? new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
methods.Add(candidateMethod);
|
||||
|
|
@ -396,9 +397,19 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
private static bool ContainsHttpMethod(List<string> httpMethods, string httpMethod)
|
||||
{
|
||||
for (var i = 0; i < httpMethods.Count; i++)
|
||||
var methods = CollectionsMarshal.AsSpan(httpMethods);
|
||||
for (var i = 0; i < methods.Length; i++)
|
||||
{
|
||||
if (string.Equals(httpMethods[i], httpMethod, StringComparison.OrdinalIgnoreCase))
|
||||
// This is a fast path for when everything is using static HttpMethods instances.
|
||||
if (object.ReferenceEquals(methods[i], httpMethod))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < methods.Length; i++)
|
||||
{
|
||||
if (HttpMethods.Equals(methods[i], httpMethod))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
@ -437,7 +448,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var httpMethod = httpContext.Request.Method;
|
||||
var headers = httpContext.Request.Headers;
|
||||
if (_supportsCorsPreflight &&
|
||||
string.Equals(httpMethod, PreflightHttpMethod, StringComparison.OrdinalIgnoreCase) &&
|
||||
HttpMethods.Equals(httpMethod, PreflightHttpMethod) &&
|
||||
headers.ContainsKey(HeaderNames.Origin) &&
|
||||
headers.TryGetValue(HeaderNames.AccessControlRequestMethod, out var accessControlRequestMethod) &&
|
||||
!StringValues.IsNullOrEmpty(accessControlRequestMethod))
|
||||
|
|
@ -499,7 +510,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
{
|
||||
return
|
||||
IsCorsPreflightRequest == other.IsCorsPreflightRequest &&
|
||||
string.Equals(HttpMethod, other.HttpMethod, StringComparison.OrdinalIgnoreCase);
|
||||
HttpMethods.Equals(HttpMethod, other.HttpMethod);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
@ -49,7 +49,7 @@ namespace Swaggatherer
|
|||
|
||||
if (entry.Method != null)
|
||||
{
|
||||
setupRequestsLines.Add($" Requests[{i}].Request.Method = \"{entries[i].Method.ToUpperInvariant()}\";");
|
||||
setupRequestsLines.Add($" Requests[{i}].Request.Method = HttpMethods.GetCanonicalizedValue({entries[i].Method});");
|
||||
}
|
||||
|
||||
setupRequestsLines.Add($" Requests[{i}].Request.Path = \"{entries[i].RequestUrl}\";");
|
||||
|
|
|
|||
Loading…
Reference in New Issue