From 187eacd570cb925715f8f722a4d09674fad6ab1d Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 19 Aug 2020 09:28:01 -0700 Subject: [PATCH 1/8] Reenable concurrent read/write test (#25017) --- .../Inprocess/SynchronousReadAndWriteTests.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/SynchronousReadAndWriteTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/SynchronousReadAndWriteTests.cs index e896acc67c35..fd80665c6f54 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/SynchronousReadAndWriteTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/SynchronousReadAndWriteTests.cs @@ -22,12 +22,11 @@ public SynchronousReadAndWriteTests(IISTestSiteFixture fixture): base(fixture) } [ConditionalFact] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/7341")] public async Task ReadAndWriteSynchronously() { - for (int i = 0; i < 100; i++) + var content = new StringContent(new string('a', 100000)); + for (int i = 0; i < 500; i++) { - var content = new StringContent(new string('a', 100000)); var response = await _fixture.Client.PostAsync("ReadAndWriteSynchronously", content); var responseText = await response.Content.ReadAsStringAsync(); From d2f34d6d99eb878f97e1378fbcad3bb07d7551ca Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 19 Aug 2020 09:29:52 -0700 Subject: [PATCH 2/8] Enable trimming for Microsoft.Extensions packages (#24986) * Enable trimming for Microsoft.Extensions packages Fixes https://github.com/dotnet/aspnetcore/issues/24634 * Update Microsoft.NET.Sdk.BlazorWebAssembly.csproj * Apply suggestions from code review * Fixup test --- .../WasmPublishIntegrationTest.cs | 19 +++++++++---------- ....NET.Sdk.BlazorWebAssembly.Current.targets | 2 +- .../Sdk/testassets/blazorwasm/Program.cs | 1 - 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Components/WebAssembly/Sdk/integrationtests/WasmPublishIntegrationTest.cs b/src/Components/WebAssembly/Sdk/integrationtests/WasmPublishIntegrationTest.cs index 909a71c52917..e29a31909270 100644 --- a/src/Components/WebAssembly/Sdk/integrationtests/WasmPublishIntegrationTest.cs +++ b/src/Components/WebAssembly/Sdk/integrationtests/WasmPublishIntegrationTest.cs @@ -141,8 +141,6 @@ public async Task Publish_WithScopedCss_Works() serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"), serviceWorkerContent: "// This is the production service worker", assetsManifestPath: "custom-service-worker-assets.js"); - - VerifyTypeGranularTrimming(result, blazorPublishDirectory); } [Fact] @@ -906,16 +904,17 @@ static string ParseWebFormattedHash(string webFormattedHash) private void VerifyTypeGranularTrimming(MSBuildResult result, string blazorPublishDirectory) { - var loggingAssemblyPath = Path.Combine(blazorPublishDirectory, "_framework", "Microsoft.Extensions.Logging.Abstractions.dll"); - Assert.FileExists(result, loggingAssemblyPath); + var componentsShimAssemblyPath = Path.Combine(blazorPublishDirectory, "_framework", "Microsoft.AspNetCore.Razor.Test.ComponentShim.dll"); + Assert.FileExists(result, componentsShimAssemblyPath); + + // RouteView is referenced by the app, so we expect it to be preserved + Assert.AssemblyContainsType(result, componentsShimAssemblyPath, "Microsoft.AspNetCore.Components.RouteView"); - // ILogger is referenced by the app, so we expect it to be preserved - Assert.AssemblyContainsType(result, loggingAssemblyPath, "Microsoft.Extensions.Logging.ILogger"); - // LogLevel is referenced by ILogger and therefore must be preserved. - Assert.AssemblyContainsType(result, loggingAssemblyPath, "Microsoft.Extensions.Logging.LogLevel"); + // RouteData is referenced by RouteView so we expect it to be preserved. + Assert.AssemblyContainsType(result, componentsShimAssemblyPath, "Microsoft.AspNetCore.Components.RouteData"); - // NullLogger is not referenced by the app, and should be trimmed. - Assert.AssemblyDoesNotContainType(result, loggingAssemblyPath, "Microsoft.Extensions.Logging.Abstractions.NullLogger"); + // CascadingParameterAttribute is not referenced by the app, and should be trimmed. + Assert.AssemblyDoesNotContainType(result, componentsShimAssemblyPath, "Microsoft.AspNetCore.Components.CascadingParameterAttribute"); } private static BootJsonData ReadBootJsonData(MSBuildResult result, string path) diff --git a/src/Components/WebAssembly/Sdk/src/targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets b/src/Components/WebAssembly/Sdk/src/targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets index 370d535eefe3..dd8f13bf2489 100644 --- a/src/Components/WebAssembly/Sdk/src/targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets +++ b/src/Components/WebAssembly/Sdk/src/targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets @@ -395,7 +395,7 @@ Copyright (c) .NET Foundation. All rights reserved. <_BlazorTypeGranularAssembly Include="@(ManagedAssemblyToLink)" - Condition="'%(Extension)' == '.dll' AND ($([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.')) or $([System.String]::Copy('%(Filename)').StartsWith('Microsoft.Extensions.')))"> + Condition="'%(Extension)' == '.dll' AND $([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))"> false all diff --git a/src/Components/WebAssembly/Sdk/testassets/blazorwasm/Program.cs b/src/Components/WebAssembly/Sdk/testassets/blazorwasm/Program.cs index 0784957affba..4b20dea9ded0 100644 --- a/src/Components/WebAssembly/Sdk/testassets/blazorwasm/Program.cs +++ b/src/Components/WebAssembly/Sdk/testassets/blazorwasm/Program.cs @@ -8,7 +8,6 @@ public static void Main(string[] args) { GC.KeepAlive(typeof(System.Text.Json.JsonSerializer)); GC.KeepAlive(typeof(RazorClassLibrary.Class1)); - GC.KeepAlive(typeof(Microsoft.Extensions.Logging.ILogger)); #if REFERENCE_classlibrarywithsatelliteassemblies GC.KeepAlive(typeof(classlibrarywithsatelliteassemblies.Class1)); #endif From b22512de0e5d7bda763351cfbce1fce7898d07b6 Mon Sep 17 00:00:00 2001 From: Martin Costello Date: Wed, 19 Aug 2020 17:45:52 +0100 Subject: [PATCH 3/8] Right-size Lists when created (#23714) Create new instances of List with an appropriate capacity for the items that will be added. Use Array.Empty() where appropriate, rather than create an empty list and then return it. --- .../src/Manifest/ManifestDirectory.cs | 2 +- .../src/Features/RequestCookiesFeature.cs | 2 +- .../src/Patterns/RoutePatternFactory.cs | 2 +- src/Identity/Core/src/SignInManager.cs | 23 ++++++++++++------- .../src/RequestLocalizationOptions.cs | 4 ++-- .../ApplicationModelFactory.cs | 2 +- .../ControllerActionDescriptorBuilder.cs | 2 +- .../DefaultApplicationModelProvider.cs | 2 +- src/Mvc/Mvc.Core/src/BindAttribute.cs | 2 +- src/Mvc/Mvc.Core/src/ConsumesAttribute.cs | 2 +- .../Mvc.Core/src/Formatters/InputFormatter.cs | 2 +- .../src/Formatters/OutputFormatter.cs | 4 ++-- .../src/Infrastructure/ActionSelector.cs | 10 ++++---- src/Mvc/Mvc.Core/src/ProducesAttribute.cs | 2 +- .../src/RazorReferenceManager.cs | 2 +- .../DefaultTempDataSerializer.cs | 7 +++--- .../ComponentEventHandlerLoweringPass.cs | 2 +- .../src/Application.cs | 2 +- .../src/DefaultRequestDispatcher.cs | 2 +- .../src/MetadataReaderExtensions.cs | 2 +- .../src/FindAssembliesWithReferencesTo.cs | 2 +- .../src/ReferenceResolver.cs | 2 +- .../ReadOnlyTagHelperAttributeList.cs | 6 ++--- .../ReadOnlyTagHelperAttributeList.cs | 4 +--- .../src/CertificateAuthenticationHandler.cs | 2 +- .../JwtBearer/src/JwtBearerHandler.cs | 4 ++-- .../HttpSys/src/AuthenticationManager.cs | 6 ++--- src/Servers/HttpSys/src/HttpSysListener.cs | 2 +- .../ReadOnlySequenceFactory.cs | 2 +- .../ObjectMethodExecutor.cs | 4 ++-- .../StackTrace/StackFrame/StackTraceHelper.cs | 8 +++---- .../src/Internal/HttpConnectionManager.cs | 2 +- .../server/Core/src/HubConnectionHandler.cs | 6 ++--- .../server/Core/src/HubOptionsSetup.cs | 2 +- .../Internal/DefaultHubMessageSerializer.cs | 3 ++- .../src/RedisHubLifetimeManager.cs | 4 ++-- 36 files changed, 71 insertions(+), 66 deletions(-) diff --git a/src/FileProviders/Embedded/src/Manifest/ManifestDirectory.cs b/src/FileProviders/Embedded/src/Manifest/ManifestDirectory.cs index b75653a0fb0f..3aa2429ad6dd 100644 --- a/src/FileProviders/Embedded/src/Manifest/ManifestDirectory.cs +++ b/src/FileProviders/Embedded/src/Manifest/ManifestDirectory.cs @@ -98,7 +98,7 @@ internal static void ValidateChildrenAndSetParent(ManifestEntry[] children, Mani private ManifestEntry[] CopyChildren() { - var list = new List(); + var list = new List(Children.Count); for (int i = 0; i < Children.Count; i++) { var child = Children[i]; diff --git a/src/Http/Http/src/Features/RequestCookiesFeature.cs b/src/Http/Http/src/Features/RequestCookiesFeature.cs index a89bb99f5df5..b0698ecdca52 100644 --- a/src/Http/Http/src/Features/RequestCookiesFeature.cs +++ b/src/Http/Http/src/Features/RequestCookiesFeature.cs @@ -80,7 +80,7 @@ public IRequestCookieCollection Cookies } else { - var headers = new List(); + var headers = new List(_parsedValues.Count); foreach (var pair in _parsedValues) { headers.Add(new CookieHeaderValue(pair.Key, pair.Value).ToString()); diff --git a/src/Http/Routing/src/Patterns/RoutePatternFactory.cs b/src/Http/Routing/src/Patterns/RoutePatternFactory.cs index 8832e94e599d..9534ba6f6fb0 100644 --- a/src/Http/Routing/src/Patterns/RoutePatternFactory.cs +++ b/src/Http/Routing/src/Patterns/RoutePatternFactory.cs @@ -497,7 +497,7 @@ RoutePatternPart VisitPart(RoutePatternPart part) updatedParameterPolicies = new Dictionary>(StringComparer.OrdinalIgnoreCase); } - parameterConstraints = new List(); + parameterConstraints = new List(parameter.ParameterPolicies.Count); updatedParameterPolicies.Add(parameter.Name, parameterConstraints); } diff --git a/src/Identity/Core/src/SignInManager.cs b/src/Identity/Core/src/SignInManager.cs index f523b3319b5e..32ad02a16dfd 100644 --- a/src/Identity/Core/src/SignInManager.cs +++ b/src/Identity/Core/src/SignInManager.cs @@ -169,16 +169,22 @@ public virtual async Task CanSignInAsync(TUser user) public virtual async Task RefreshSignInAsync(TUser user) { var auth = await Context.AuthenticateAsync(IdentityConstants.ApplicationScheme); - var claims = new List(); + IList claims = Array.Empty(); + var authenticationMethod = auth?.Principal?.FindFirst(ClaimTypes.AuthenticationMethod); - if (authenticationMethod != null) - { - claims.Add(authenticationMethod); - } var amr = auth?.Principal?.FindFirst("amr"); - if (amr != null) + + if (authenticationMethod != null || amr != null) { - claims.Add(amr); + claims = new List(); + if (authenticationMethod != null) + { + claims.Add(authenticationMethod); + } + if (amr != null) + { + claims.Add(amr); + } } await SignInWithClaimsAsync(user, auth?.Properties, claims); @@ -203,9 +209,10 @@ public virtual Task SignInAsync(TUser user, bool isPersistent, string authentica /// The task object representing the asynchronous operation. public virtual Task SignInAsync(TUser user, AuthenticationProperties authenticationProperties, string authenticationMethod = null) { - var additionalClaims = new List(); + IList additionalClaims = Array.Empty(); if (authenticationMethod != null) { + additionalClaims = new List(); additionalClaims.Add(new Claim(ClaimTypes.AuthenticationMethod, authenticationMethod)); } return SignInWithClaimsAsync(user, authenticationProperties, additionalClaims); diff --git a/src/Middleware/Localization/src/RequestLocalizationOptions.cs b/src/Middleware/Localization/src/RequestLocalizationOptions.cs index 95ca74fe32b5..c77c10dfe523 100644 --- a/src/Middleware/Localization/src/RequestLocalizationOptions.cs +++ b/src/Middleware/Localization/src/RequestLocalizationOptions.cs @@ -122,7 +122,7 @@ public RequestCulture DefaultRequestCulture /// The . public RequestLocalizationOptions AddSupportedCultures(params string[] cultures) { - var supportedCultures = new List(); + var supportedCultures = new List(cultures.Length); foreach (var culture in cultures) { @@ -140,7 +140,7 @@ public RequestLocalizationOptions AddSupportedCultures(params string[] cultures) /// The . public RequestLocalizationOptions AddSupportedUICultures(params string[] uiCultures) { - var supportedUICultures = new List(); + var supportedUICultures = new List(uiCultures.Length); foreach (var culture in uiCultures) { supportedUICultures.Add(new CultureInfo(culture)); diff --git a/src/Mvc/Mvc.Core/src/ApplicationModels/ApplicationModelFactory.cs b/src/Mvc/Mvc.Core/src/ApplicationModels/ApplicationModelFactory.cs index 88f8ce93f4f4..842292f476c9 100644 --- a/src/Mvc/Mvc.Core/src/ApplicationModels/ApplicationModelFactory.cs +++ b/src/Mvc/Mvc.Core/src/ApplicationModels/ApplicationModelFactory.cs @@ -309,7 +309,7 @@ private static string CreateMixedRoutedActionDescriptorsErrorMessage( // Text to show as the attribute route template for conventionally routed actions. var nullTemplate = Resources.AttributeRoute_NullTemplateRepresentation; - var actionDescriptions = new List(); + var actionDescriptions = new List(actions.Count); for (var i = 0; i < actions.Count; i++) { var (action, selector) = actions[i]; diff --git a/src/Mvc/Mvc.Core/src/ApplicationModels/ControllerActionDescriptorBuilder.cs b/src/Mvc/Mvc.Core/src/ApplicationModels/ControllerActionDescriptorBuilder.cs index c53234af3e0e..9b2e8a951af1 100644 --- a/src/Mvc/Mvc.Core/src/ApplicationModels/ControllerActionDescriptorBuilder.cs +++ b/src/Mvc/Mvc.Core/src/ApplicationModels/ControllerActionDescriptorBuilder.cs @@ -63,7 +63,7 @@ private static void AddControllerPropertyDescriptors(ActionDescriptor actionDesc private static void AddParameterDescriptors(ActionDescriptor actionDescriptor, ActionModel action) { - var parameterDescriptors = new List(); + var parameterDescriptors = new List(action.Parameters.Count); foreach (var parameter in action.Parameters) { var parameterDescriptor = CreateParameterDescriptor(parameter); diff --git a/src/Mvc/Mvc.Core/src/ApplicationModels/DefaultApplicationModelProvider.cs b/src/Mvc/Mvc.Core/src/ApplicationModels/DefaultApplicationModelProvider.cs index 3171c8f1aba8..f309c37ccc59 100644 --- a/src/Mvc/Mvc.Core/src/ApplicationModels/DefaultApplicationModelProvider.cs +++ b/src/Mvc/Mvc.Core/src/ApplicationModels/DefaultApplicationModelProvider.cs @@ -352,7 +352,7 @@ internal ActionModel CreateActionModel( // This is fairly complicated so that we maintain referential equality between items in // ActionModel.Attributes and ActionModel.Attributes[*].Attribute. - var applicableAttributes = new List(); + var applicableAttributes = new List(routeAttributes.Length); foreach (var attribute in attributes) { if (attribute is IRouteTemplateProvider) diff --git a/src/Mvc/Mvc.Core/src/BindAttribute.cs b/src/Mvc/Mvc.Core/src/BindAttribute.cs index 691c81ac7cac..e4f1ab2bc7d4 100644 --- a/src/Mvc/Mvc.Core/src/BindAttribute.cs +++ b/src/Mvc/Mvc.Core/src/BindAttribute.cs @@ -25,7 +25,7 @@ public class BindAttribute : Attribute, IModelNameProvider, IPropertyFilterProvi /// Names of parameters to include in binding. public BindAttribute(params string[] include) { - var items = new List(); + var items = new List(include.Length); foreach (var item in include) { items.AddRange(SplitString(item)); diff --git a/src/Mvc/Mvc.Core/src/ConsumesAttribute.cs b/src/Mvc/Mvc.Core/src/ConsumesAttribute.cs index 12afb98c3245..14bcf6862668 100644 --- a/src/Mvc/Mvc.Core/src/ConsumesAttribute.cs +++ b/src/Mvc/Mvc.Core/src/ConsumesAttribute.cs @@ -199,7 +199,7 @@ private bool IsApplicable(ActionDescriptor actionDescriptor) private MediaTypeCollection GetContentTypes(string firstArg, string[] args) { - var completeArgs = new List(); + var completeArgs = new List(args.Length + 1); completeArgs.Add(firstArg); completeArgs.AddRange(args); var contentTypes = new MediaTypeCollection(); diff --git a/src/Mvc/Mvc.Core/src/Formatters/InputFormatter.cs b/src/Mvc/Mvc.Core/src/Formatters/InputFormatter.cs index 2392b5a47c0c..93fac14855bc 100644 --- a/src/Mvc/Mvc.Core/src/Formatters/InputFormatter.cs +++ b/src/Mvc/Mvc.Core/src/Formatters/InputFormatter.cs @@ -159,7 +159,7 @@ public virtual IReadOnlyList GetSupportedContentTypes(string contentType { if (mediaTypes == null) { - mediaTypes = new List(); + mediaTypes = new List(SupportedMediaTypes.Count); } mediaTypes.Add(mediaType); diff --git a/src/Mvc/Mvc.Core/src/Formatters/OutputFormatter.cs b/src/Mvc/Mvc.Core/src/Formatters/OutputFormatter.cs index fd0c37b972c8..53cfa8706a58 100644 --- a/src/Mvc/Mvc.Core/src/Formatters/OutputFormatter.cs +++ b/src/Mvc/Mvc.Core/src/Formatters/OutputFormatter.cs @@ -67,7 +67,7 @@ public virtual IReadOnlyList GetSupportedContentTypes( { if (mediaTypes == null) { - mediaTypes = new List(); + mediaTypes = new List(SupportedMediaTypes.Count); } mediaTypes.Add(contentType); @@ -81,7 +81,7 @@ public virtual IReadOnlyList GetSupportedContentTypes( { if (mediaTypes == null) { - mediaTypes = new List(); + mediaTypes = new List(SupportedMediaTypes.Count); } mediaTypes.Add(mediaType); diff --git a/src/Mvc/Mvc.Core/src/Infrastructure/ActionSelector.cs b/src/Mvc/Mvc.Core/src/Infrastructure/ActionSelector.cs index 2912f9ec85ff..72600a980c36 100644 --- a/src/Mvc/Mvc.Core/src/Infrastructure/ActionSelector.cs +++ b/src/Mvc/Mvc.Core/src/Infrastructure/ActionSelector.cs @@ -135,10 +135,11 @@ private IReadOnlyList EvaluateActionConstraints( RouteContext context, IReadOnlyList actions) { - var candidates = new List(); + var actionsCount = actions.Count; + var candidates = new List(actionsCount); // Perf: Avoid allocations - for (var i = 0; i < actions.Count; i++) + for (var i = 0; i < actionsCount; i++) { var action = actions[i]; var constraints = _actionConstraintCache.GetActionConstraints(context.HttpContext, action); @@ -150,9 +151,10 @@ private IReadOnlyList EvaluateActionConstraints( List results = null; if (matches != null) { - results = new List(matches.Count); + var matchesCount = matches.Count; + results = new List(matchesCount); // Perf: Avoid allocations - for (var i = 0; i < matches.Count; i++) + for (var i = 0; i < matchesCount; i++) { var candidate = matches[i]; results.Add(candidate.Action); diff --git a/src/Mvc/Mvc.Core/src/ProducesAttribute.cs b/src/Mvc/Mvc.Core/src/ProducesAttribute.cs index 24218189e59c..aa0429cc7918 100644 --- a/src/Mvc/Mvc.Core/src/ProducesAttribute.cs +++ b/src/Mvc/Mvc.Core/src/ProducesAttribute.cs @@ -111,7 +111,7 @@ public void SetContentTypes(MediaTypeCollection contentTypes) private MediaTypeCollection GetContentTypes(string firstArg, string[] args) { - var completeArgs = new List(); + var completeArgs = new List(args.Length + 1); completeArgs.Add(firstArg); completeArgs.AddRange(args); var contentTypes = new MediaTypeCollection(); diff --git a/src/Mvc/Mvc.Razor.RuntimeCompilation/src/RazorReferenceManager.cs b/src/Mvc/Mvc.Razor.RuntimeCompilation/src/RazorReferenceManager.cs index b672dbaff60b..99151aea0ed9 100644 --- a/src/Mvc/Mvc.Razor.RuntimeCompilation/src/RazorReferenceManager.cs +++ b/src/Mvc/Mvc.Razor.RuntimeCompilation/src/RazorReferenceManager.cs @@ -52,7 +52,7 @@ private IReadOnlyList GetCompilationReferences() // For unit testing internal IEnumerable GetReferencePaths() { - var referencePaths = new List(); + var referencePaths = new List(_options.AdditionalReferencePaths.Count); foreach (var part in _partManager.ApplicationParts) { diff --git a/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/DefaultTempDataSerializer.cs b/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/DefaultTempDataSerializer.cs index 4ca26e540a5b..2f2af39b1b55 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/DefaultTempDataSerializer.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/DefaultTempDataSerializer.cs @@ -83,7 +83,8 @@ private IDictionary DeserializeDictionary(JsonElement rootElemen private static object DeserializeArray(in JsonElement arrayElement) { - if (arrayElement.GetArrayLength() == 0) + int arrayLength = arrayElement.GetArrayLength(); + if (arrayLength == 0) { // We have to infer the type of the array by inspecting it's elements. // If there's nothing to inspect, return a null value since we do not know @@ -93,7 +94,7 @@ private static object DeserializeArray(in JsonElement arrayElement) if (arrayElement[0].ValueKind == JsonValueKind.String) { - var array = new List(); + var array = new List(arrayLength); foreach (var item in arrayElement.EnumerateArray()) { @@ -104,7 +105,7 @@ private static object DeserializeArray(in JsonElement arrayElement) } else if (arrayElement[0].ValueKind == JsonValueKind.Number) { - var array = new List(); + var array = new List(arrayLength); foreach (var item in arrayElement.EnumerateArray()) { diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentEventHandlerLoweringPass.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentEventHandlerLoweringPass.cs index 2e72dfe39792..dd6a18e5e305 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentEventHandlerLoweringPass.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentEventHandlerLoweringPass.cs @@ -170,7 +170,7 @@ private IntermediateNode RewriteUsage(IntermediateNode parent, TagHelperDirectiv // This method is overloaded on string and T, which means that it will put the code in the // correct context for intellisense when typing in the attribute. var eventArgsType = node.TagHelper.GetEventArgsType(); - var tokens = new List() + var tokens = new List(original.Count + 2) { new IntermediateToken() { diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/Application.cs b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/Application.cs index de4870f82396..0d03cba384f8 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/Application.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/Application.cs @@ -96,7 +96,7 @@ private string GetInformationalVersion() private static string[] ExpandResponseFiles(string[] args) { - var expandedArgs = new List(); + var expandedArgs = new List(args.Length); foreach (var arg in args) { if (!arg.StartsWith("@", StringComparison.Ordinal)) diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/DefaultRequestDispatcher.cs b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/DefaultRequestDispatcher.cs index 59d2bbc09840..110494b24937 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/DefaultRequestDispatcher.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/DefaultRequestDispatcher.cs @@ -143,7 +143,7 @@ private void HandleCancellation() /// private void WaitForAnyCompletion(CancellationToken cancellationToken) { - var all = new List(); + var all = new List(_connections.Count + 3); all.AddRange(_connections); all.Add(_timeoutTask); all.Add(_listenTask); diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/MetadataReaderExtensions.cs b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/MetadataReaderExtensions.cs index 17da6b63be05..7becdaca4143 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/MetadataReaderExtensions.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/MetadataReaderExtensions.cs @@ -33,7 +33,7 @@ internal static AssemblyIdentity GetAssemblyIdentity(this MetadataReader reader) internal static AssemblyIdentity[] GetReferencedAssembliesOrThrow(this MetadataReader reader) { - var references = new List(); + var references = new List(reader.AssemblyReferences.Count); foreach (var referenceHandle in reader.AssemblyReferences) { diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/FindAssembliesWithReferencesTo.cs b/src/Razor/Microsoft.NET.Sdk.Razor/src/FindAssembliesWithReferencesTo.cs index cb1d2359e8f2..b9e03c0791ef 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/src/FindAssembliesWithReferencesTo.cs +++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/FindAssembliesWithReferencesTo.cs @@ -23,7 +23,7 @@ public class FindAssembliesWithReferencesTo : Task public override bool Execute() { - var referenceItems = new List(); + var referenceItems = new List(Assemblies.Length); foreach (var item in Assemblies) { const string FusionNameKey = "FusionName"; diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/ReferenceResolver.cs b/src/Razor/Microsoft.NET.Sdk.Razor/src/ReferenceResolver.cs index 6b1489cab0df..c259b70a83a8 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/src/ReferenceResolver.cs +++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/ReferenceResolver.cs @@ -120,7 +120,7 @@ protected virtual IReadOnlyList GetReferences(string file) var metadataReader = peReader.GetMetadataReader(); - var references = new List(); + var references = new List(metadataReader.AssemblyReferences.Count); foreach (var handle in metadataReader.AssemblyReferences) { var reference = metadataReader.GetAssemblyReference(handle); diff --git a/src/Razor/Razor/src/TagHelpers/ReadOnlyTagHelperAttributeList.cs b/src/Razor/Razor/src/TagHelpers/ReadOnlyTagHelperAttributeList.cs index 2b0b55d1f18c..376b21081621 100644 --- a/src/Razor/Razor/src/TagHelpers/ReadOnlyTagHelperAttributeList.cs +++ b/src/Razor/Razor/src/TagHelpers/ReadOnlyTagHelperAttributeList.cs @@ -12,8 +12,6 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers /// public abstract class ReadOnlyTagHelperAttributeList : ReadOnlyCollection { - private static readonly IReadOnlyList EmptyList = new TagHelperAttribute[0]; - /// /// Instantiates a new instance of with an empty /// collection. @@ -146,7 +144,7 @@ public bool TryGetAttributes(string name, out IReadOnlyList matchedAttributes.Add(attribute); } } - attributes = matchedAttributes ?? EmptyList; + attributes = matchedAttributes ?? (IReadOnlyList)Array.Empty(); return matchedAttributes != null; } @@ -198,4 +196,4 @@ protected static bool NameEquals(string name, TagHelperAttribute attribute) return string.Equals(name, attribute.Name, StringComparison.OrdinalIgnoreCase); } } -} \ No newline at end of file +} diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.MvcShim/Microsoft.AspNetCore.Razor.TagHelpers/ReadOnlyTagHelperAttributeList.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.MvcShim/Microsoft.AspNetCore.Razor.TagHelpers/ReadOnlyTagHelperAttributeList.cs index f3d1569a593a..b866dff7db58 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.MvcShim/Microsoft.AspNetCore.Razor.TagHelpers/ReadOnlyTagHelperAttributeList.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.MvcShim/Microsoft.AspNetCore.Razor.TagHelpers/ReadOnlyTagHelperAttributeList.cs @@ -12,8 +12,6 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers /// public abstract class ReadOnlyTagHelperAttributeList : ReadOnlyCollection { - private static readonly IReadOnlyList EmptyList = new TagHelperAttribute[0]; - /// /// Instantiates a new instance of with an empty /// collection. @@ -138,7 +136,7 @@ public bool TryGetAttributes(string name, out IReadOnlyList matchedAttributes.Add(Items[i]); } } - attributes = matchedAttributes ?? EmptyList; + attributes = matchedAttributes ?? Array.Empty() as IReadOnlyList; return matchedAttributes != null; } diff --git a/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs b/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs index caf77f982a40..91a6cc49bae5 100644 --- a/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs +++ b/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs @@ -131,7 +131,7 @@ private async Task ValidateCertificateAsync(X509Certificate2 var certificateIsValid = chain.Build(clientCertificate); if (!certificateIsValid) { - var chainErrors = new List(); + var chainErrors = new List(chain.ChainStatus.Length); foreach (var validationFailure in chain.ChainStatus) { chainErrors.Add($"{validationFailure.Status} {validationFailure.StatusInformation}"); diff --git a/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs b/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs index a190613d9dea..f3c851e929a1 100644 --- a/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs +++ b/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs @@ -274,7 +274,7 @@ protected override Task HandleForbiddenAsync(AuthenticationProperties properties private static string CreateErrorDescription(Exception authFailure) { - IEnumerable exceptions; + IReadOnlyCollection exceptions; if (authFailure is AggregateException agEx) { exceptions = agEx.InnerExceptions; @@ -284,7 +284,7 @@ private static string CreateErrorDescription(Exception authFailure) exceptions = new[] { authFailure }; } - var messages = new List(); + var messages = new List(exceptions.Count); foreach (var ex in exceptions) { diff --git a/src/Servers/HttpSys/src/AuthenticationManager.cs b/src/Servers/HttpSys/src/AuthenticationManager.cs index f8ffc49af12d..a15ff2192fd7 100644 --- a/src/Servers/HttpSys/src/AuthenticationManager.cs +++ b/src/Servers/HttpSys/src/AuthenticationManager.cs @@ -107,13 +107,13 @@ private unsafe void SetUrlGroupSecurity() internal static IList GenerateChallenges(AuthenticationSchemes authSchemes) { - IList challenges = new List(); - if (authSchemes == AuthenticationSchemes.None) { - return challenges; + return Array.Empty(); } + IList challenges = new List(); + // Order by strength. if ((authSchemes & AuthenticationSchemes.Kerberos) == AuthenticationSchemes.Kerberos) { diff --git a/src/Servers/HttpSys/src/HttpSysListener.cs b/src/Servers/HttpSys/src/HttpSysListener.cs index 89ab34538943..4f4694ff5ce2 100644 --- a/src/Servers/HttpSys/src/HttpSysListener.cs +++ b/src/Servers/HttpSys/src/HttpSysListener.cs @@ -343,7 +343,7 @@ internal unsafe void SendError(ulong requestId, int httpStatusCode, IList 0) { - pinnedHeaders = new List(); + pinnedHeaders = new List(authChallenges.Count + 3); HttpApiTypes.HTTP_RESPONSE_INFO[] knownHeaderInfo = null; knownHeaderInfo = new HttpApiTypes.HTTP_RESPONSE_INFO[1]; diff --git a/src/Shared/Buffers.Testing/ReadOnlySequenceFactory.cs b/src/Shared/Buffers.Testing/ReadOnlySequenceFactory.cs index 9ffeab03b91d..0a4faef2dab9 100644 --- a/src/Shared/Buffers.Testing/ReadOnlySequenceFactory.cs +++ b/src/Shared/Buffers.Testing/ReadOnlySequenceFactory.cs @@ -92,7 +92,7 @@ public override ReadOnlySequence CreateOfSize(int size) public override ReadOnlySequence CreateWithContent(byte[] data) { - var segments = new List(); + var segments = new List((data.Length * 2) + 1); segments.Add(Array.Empty()); foreach (var b in data) diff --git a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs index 084f7f0aad07..a3abd893dc50 100644 --- a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs +++ b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs @@ -157,8 +157,8 @@ private static MethodExecutor GetExecutor(MethodInfo methodInfo, TypeInfo target var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); // Build parameter list - var parameters = new List(); var paramInfos = methodInfo.GetParameters(); + var parameters = new List(paramInfos.Length); for (int i = 0; i < paramInfos.Length; i++) { var paramInfo = paramInfos[i]; @@ -209,8 +209,8 @@ private static MethodExecutorAsync GetExecutorAsync( var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); // Build parameter list - var parameters = new List(); var paramInfos = methodInfo.GetParameters(); + var parameters = new List(paramInfos.Length); for (int i = 0; i < paramInfos.Length; i++) { var paramInfo = paramInfos[i]; diff --git a/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs b/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs index 5c38980109b3..3f8249a576a8 100644 --- a/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs +++ b/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs @@ -18,12 +18,10 @@ internal class StackTraceHelper { public static IList GetFrames(Exception exception, out AggregateException? error) { - var frames = new List(); - if (exception == null) { error = default; - return frames; + return Array.Empty(); } var needFileInfo = true; @@ -33,9 +31,11 @@ public static IList GetFrames(Exception exception, out Aggregate if (stackFrames == null) { error = default; - return frames; + return Array.Empty(); } + var frames = new List(stackFrames.Length); + List? exceptions = null; for (var i = 0; i < stackFrames.Length; i++) diff --git a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionManager.cs b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionManager.cs index 84f62abdc009..de7feb973a0b 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionManager.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionManager.cs @@ -180,7 +180,7 @@ public void CloseConnections() // Stop firing the timer _nextHeartbeat.Stop(); - var tasks = new List(); + var tasks = new List(_connections.Count); // REVIEW: In the future we can consider a hybrid where we first try to wait for shutdown // for a certain time frame then after some grace period we shutdown more aggressively diff --git a/src/SignalR/server/Core/src/HubConnectionHandler.cs b/src/SignalR/server/Core/src/HubConnectionHandler.cs index c5970a96a6de..40745494ad2d 100644 --- a/src/SignalR/server/Core/src/HubConnectionHandler.cs +++ b/src/SignalR/server/Core/src/HubConnectionHandler.cs @@ -73,8 +73,7 @@ IServiceScopeFactory serviceScopeFactory if (_hubOptions.HubFilters != null) { - hubFilters = new List(); - hubFilters.AddRange(_hubOptions.HubFilters); + hubFilters = new List(_hubOptions.HubFilters); } } else @@ -84,8 +83,7 @@ IServiceScopeFactory serviceScopeFactory if (_globalHubOptions.HubFilters != null) { - hubFilters = new List(); - hubFilters.AddRange(_globalHubOptions.HubFilters); + hubFilters = new List(_globalHubOptions.HubFilters); } } diff --git a/src/SignalR/server/Core/src/HubOptionsSetup.cs b/src/SignalR/server/Core/src/HubOptionsSetup.cs index 6b6a08abab0c..c41fd8fb49e8 100644 --- a/src/SignalR/server/Core/src/HubOptionsSetup.cs +++ b/src/SignalR/server/Core/src/HubOptionsSetup.cs @@ -56,7 +56,7 @@ public void Configure(HubOptions options) if (options.SupportedProtocols == null) { - options.SupportedProtocols = new List(); + options.SupportedProtocols = new List(_defaultProtocols.Count); } if (options.StreamBufferCapacity == null) diff --git a/src/SignalR/server/StackExchangeRedis/src/Internal/DefaultHubMessageSerializer.cs b/src/SignalR/server/StackExchangeRedis/src/Internal/DefaultHubMessageSerializer.cs index 7bcd4089e8e9..13bcc6f4900e 100644 --- a/src/SignalR/server/StackExchangeRedis/src/Internal/DefaultHubMessageSerializer.cs +++ b/src/SignalR/server/StackExchangeRedis/src/Internal/DefaultHubMessageSerializer.cs @@ -10,11 +10,12 @@ namespace Microsoft.AspNetCore.SignalR.Internal { internal class DefaultHubMessageSerializer { - private readonly List _hubProtocols = new List(); + private readonly List _hubProtocols; public DefaultHubMessageSerializer(IHubProtocolResolver hubProtocolResolver, IList globalSupportedProtocols, IList hubSupportedProtocols) { var supportedProtocols = hubSupportedProtocols ?? globalSupportedProtocols ?? Array.Empty(); + _hubProtocols = new List(supportedProtocols.Count); foreach (var protocolName in supportedProtocols) { var protocol = hubProtocolResolver.GetProtocol(protocolName, (supportedProtocols as IReadOnlyList) ?? supportedProtocols.ToList()); diff --git a/src/SignalR/server/StackExchangeRedis/src/RedisHubLifetimeManager.cs b/src/SignalR/server/StackExchangeRedis/src/RedisHubLifetimeManager.cs index df81ed61b3d3..b2e5b1f8fb7b 100644 --- a/src/SignalR/server/StackExchangeRedis/src/RedisHubLifetimeManager.cs +++ b/src/SignalR/server/StackExchangeRedis/src/RedisHubLifetimeManager.cs @@ -465,7 +465,7 @@ private Task SubscribeToUser(HubConnectionContext connection) { var invocation = _protocol.ReadInvocation((byte[])channelMessage.Message); - var tasks = new List(); + var tasks = new List(subscriptions.Count); foreach (var userConnection in subscriptions) { tasks.Add(userConnection.WriteAsync(invocation.Message).AsTask()); @@ -491,7 +491,7 @@ private async Task SubscribeToGroupAsync(string groupChannel, HubConnectionStore { var invocation = _protocol.ReadInvocation((byte[])channelMessage.Message); - var tasks = new List(); + var tasks = new List(groupConnections.Count); foreach (var groupConnection in groupConnections) { if (invocation.ExcludedConnectionIds?.Contains(groupConnection.ConnectionId) == true) From c6814f4f4d2b90ae2830035a9b6cbdcd3604dd56 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 19 Aug 2020 10:00:08 -0700 Subject: [PATCH 4/8] Enable the compatibility analyzer for Blazor WebAssembly (#24978) See https://github.com/dotnet/sdk/pull/12872#issuecomment-673783153 --- .../Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Components/WebAssembly/Sdk/src/targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets b/src/Components/WebAssembly/Sdk/src/targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets index dd8f13bf2489..13982d548aee 100644 --- a/src/Components/WebAssembly/Sdk/src/targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets +++ b/src/Components/WebAssembly/Sdk/src/targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets @@ -69,6 +69,12 @@ Copyright (c) .NET Foundation. All rights reserved. + + + + + + From b140c09c02c3e197b8aed3e4780483523f654eb5 Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Wed, 19 Aug 2020 10:27:18 -0700 Subject: [PATCH 5/8] Add support delegating requests in HttpSysServer (#24857) * Add new ctors for RequestQueue and UrlGroup * Add DelegateRequest pinvokes * Add Request Transfer Feature * Fix accessibility of feature * Test cleanup * Update ref assembly * hack: Make HttpSysServer packable * Cleanup based on PR feedback * Avoid sending headers after transfer * Fix ref assembly * Fix rebase conflict * Switch to DelegateRequestEx * Add feature detection * Delete ref folder * Add server feature * s/RequestQueueWrapper/DelegationRule * Fix UrlGroup was null issue * Add light-up for ServerDelegationPropertyFeature * Revert changes to sample * Revert changes to sample take 2 * PR feedback * s/Transfered/Transferred * DelegateAfterRequestBodyReadShouldThrow * Make DelegationRule disposable * More license headers * Incomplete XML doc * PR feedback * Fix broken test * PR feedback * Fixup test * s/Transfer/Delegate * s/transfer/delegate * PR feedback --- src/Servers/HttpSys/src/AsyncAcceptContext.cs | 4 +- src/Servers/HttpSys/src/DelegationRule.cs | 40 ++++ src/Servers/HttpSys/src/FeatureContext.cs | 11 +- .../src/IHttpSysRequestDelegationFeature.cs | 20 ++ .../HttpSys/src/IServerDelegationFeature.cs | 16 ++ src/Servers/HttpSys/src/MessagePump.cs | 6 + .../HttpSys/src/NativeInterop/HttpApi.cs | 21 ++ .../HttpSys/src/NativeInterop/RequestQueue.cs | 34 +++- .../HttpSys/src/NativeInterop/UrlGroup.cs | 26 +++ .../HttpSys/src/RequestProcessing/Request.cs | 45 +++-- .../src/RequestProcessing/RequestContext.cs | 43 ++++- .../HttpSys/src/RequestProcessing/Response.cs | 6 + .../src/RequestProcessing/ResponseBody.cs | 5 + .../src/ServerDelegationPropertyFeature.cs | 29 +++ .../HttpSys/src/StandardFeatureCollection.cs | 6 + .../DelegateSupportedConditionAttribute.cs | 27 +++ .../test/FunctionalTests/DelegateTests.cs | 179 ++++++++++++++++++ .../HttpSys/NativeInterop/HttpApiTypes.cs | 24 +++ 18 files changed, 511 insertions(+), 31 deletions(-) create mode 100644 src/Servers/HttpSys/src/DelegationRule.cs create mode 100644 src/Servers/HttpSys/src/IHttpSysRequestDelegationFeature.cs create mode 100644 src/Servers/HttpSys/src/IServerDelegationFeature.cs create mode 100644 src/Servers/HttpSys/src/ServerDelegationPropertyFeature.cs create mode 100644 src/Servers/HttpSys/test/FunctionalTests/DelegateSupportedConditionAttribute.cs create mode 100644 src/Servers/HttpSys/test/FunctionalTests/DelegateTests.cs diff --git a/src/Servers/HttpSys/src/AsyncAcceptContext.cs b/src/Servers/HttpSys/src/AsyncAcceptContext.cs index f3f5bc14cded..c313f9124e94 100644 --- a/src/Servers/HttpSys/src/AsyncAcceptContext.cs +++ b/src/Servers/HttpSys/src/AsyncAcceptContext.cs @@ -127,7 +127,9 @@ internal uint QueueBeginGetContext() statusCode = HttpApi.HttpReceiveHttpRequest( Server.RequestQueue.Handle, _nativeRequestContext.RequestId, - (uint)HttpApiTypes.HTTP_FLAGS.HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY, + // Small perf impact by not using HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY + // if the request sends header+body in a single TCP packet + (uint)HttpApiTypes.HTTP_FLAGS.NONE, _nativeRequestContext.NativeRequest, _nativeRequestContext.Size, &bytesTransferred, diff --git a/src/Servers/HttpSys/src/DelegationRule.cs b/src/Servers/HttpSys/src/DelegationRule.cs new file mode 100644 index 000000000000..593b88456a25 --- /dev/null +++ b/src/Servers/HttpSys/src/DelegationRule.cs @@ -0,0 +1,40 @@ +// 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 Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.HttpSys +{ + /// + /// Rule that maintains a handle to the Request Queue and UrlPrefix to + /// delegate to. + /// + public class DelegationRule : IDisposable + { + private readonly ILogger _logger; + /// + /// The name of the Http.Sys request queue + /// + public string QueueName { get; } + /// + /// The URL of the Http.Sys Url Prefix + /// + public string UrlPrefix { get; } + internal RequestQueue Queue { get; } + + internal DelegationRule(string queueName, string urlPrefix, ILogger logger) + { + _logger = logger; + QueueName = queueName; + UrlPrefix = urlPrefix; + Queue = new RequestQueue(queueName, UrlPrefix, _logger, receiver: true); + } + + public void Dispose() + { + Queue.UrlGroup?.Dispose(); + Queue?.Dispose(); + } + } +} diff --git a/src/Servers/HttpSys/src/FeatureContext.cs b/src/Servers/HttpSys/src/FeatureContext.cs index 4845013a6b13..3d505c157871 100644 --- a/src/Servers/HttpSys/src/FeatureContext.cs +++ b/src/Servers/HttpSys/src/FeatureContext.cs @@ -37,7 +37,8 @@ internal class FeatureContext : IHttpBodyControlFeature, IHttpSysRequestInfoFeature, IHttpResponseTrailersFeature, - IHttpResetFeature + IHttpResetFeature, + IHttpSysRequestDelegationFeature { private RequestContext _requestContext; private IFeatureCollection _features; @@ -591,6 +592,8 @@ IHeaderDictionary IHttpResponseTrailersFeature.Trailers set => _responseTrailers = value; } + public bool CanDelegate => Request.CanDelegate; + internal async Task OnResponseStart() { if (_responseStarted) @@ -711,5 +714,11 @@ private async Task NotifyOnCompletedAsync() await actionPair.Item1(actionPair.Item2); } } + + public void DelegateRequest(DelegationRule destination) + { + _requestContext.Delegate(destination); + _responseStarted = true; + } } } diff --git a/src/Servers/HttpSys/src/IHttpSysRequestDelegationFeature.cs b/src/Servers/HttpSys/src/IHttpSysRequestDelegationFeature.cs new file mode 100644 index 000000000000..a581cc8683de --- /dev/null +++ b/src/Servers/HttpSys/src/IHttpSysRequestDelegationFeature.cs @@ -0,0 +1,20 @@ +// 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. + +namespace Microsoft.AspNetCore.Server.HttpSys +{ + public interface IHttpSysRequestDelegationFeature + { + /// + /// Indicates if the server can delegate this request to another HttpSys request queue. + /// + bool CanDelegate { get; } + + /// + /// Attempt to delegate the request to another Http.Sys request queue. The request body + /// must not be read nor the response started before this is invoked. Check + /// before invoking. + /// + void DelegateRequest(DelegationRule destination); + } +} diff --git a/src/Servers/HttpSys/src/IServerDelegationFeature.cs b/src/Servers/HttpSys/src/IServerDelegationFeature.cs new file mode 100644 index 000000000000..7353f9f05345 --- /dev/null +++ b/src/Servers/HttpSys/src/IServerDelegationFeature.cs @@ -0,0 +1,16 @@ +// 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. + +namespace Microsoft.AspNetCore.Server.HttpSys +{ + public interface IServerDelegationFeature + { + /// + /// Create a delegation rule on request queue owned by the server. + /// + /// + /// Creates a that can used to delegate individual requests. + /// + DelegationRule CreateDelegationRule(string queueName, string urlPrefix); + } +} diff --git a/src/Servers/HttpSys/src/MessagePump.cs b/src/Servers/HttpSys/src/MessagePump.cs index 8de84fbb0517..c3ccdda86f45 100644 --- a/src/Servers/HttpSys/src/MessagePump.cs +++ b/src/Servers/HttpSys/src/MessagePump.cs @@ -55,6 +55,12 @@ public MessagePump(IOptions options, ILoggerFactory loggerFactor _serverAddresses = new ServerAddressesFeature(); Features.Set(_serverAddresses); + if (HttpApi.IsFeatureSupported(HttpApiTypes.HTTP_FEATURE_ID.HttpFeatureDelegateEx)) + { + var delegationProperty = new ServerDelegationPropertyFeature(Listener.RequestQueue, _logger); + Features.Set(delegationProperty); + } + _maxAccepts = _options.MaxAccepts; } diff --git a/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs b/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs index 6781465bc24f..afa2c7c2ff7f 100644 --- a/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs +++ b/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs @@ -45,6 +45,9 @@ internal static unsafe class HttpApi [DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)] internal static extern uint HttpCreateUrlGroup(ulong serverSessionId, ulong* urlGroupId, uint reserved); + [DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern uint HttpFindUrlGroupId(string pFullyQualifiedUrl, SafeHandle requestQueueHandle, ulong* urlGroupId); + [DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] internal static extern uint HttpAddUrlToUrlGroup(ulong urlGroupId, string pFullyQualifiedUrl, ulong context, uint pReserved); @@ -70,6 +73,13 @@ internal static extern unsafe uint HttpCreateRequestQueue(HTTPAPI_VERSION versio [DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)] internal static extern unsafe uint HttpCloseRequestQueue(IntPtr pReqQueueHandle); + [DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)] + internal static extern bool HttpIsFeatureSupported(HTTP_FEATURE_ID feature); + + [DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern unsafe uint HttpDelegateRequestEx(SafeHandle pReqQueueHandle, SafeHandle pDelegateQueueHandle, ulong requestId, + ulong delegateUrlGroupId, ulong propertyInfoSetSize, HTTP_DELEGATE_REQUEST_PROPERTY_INFO* pRequestPropertyBuffer); + internal delegate uint HttpSetRequestPropertyInvoker(SafeHandle requestQueueHandle, ulong requestId, HTTP_REQUEST_PROPERTY propertyId, void* input, uint inputSize, IntPtr overlapped); private static HTTPAPI_VERSION version; @@ -145,5 +155,16 @@ internal static bool Supported return supported; } } + + internal static bool IsFeatureSupported(HTTP_FEATURE_ID feature) + { + try + { + return HttpIsFeatureSupported(feature); + } + catch (EntryPointNotFoundException) { } + + return false; + } } } diff --git a/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs b/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs index 4fb2d602808a..ca3d2e79407a 100644 --- a/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs +++ b/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs @@ -16,22 +16,44 @@ internal class RequestQueue Marshal.SizeOf(); private readonly RequestQueueMode _mode; - private readonly UrlGroup _urlGroup; private readonly ILogger _logger; private bool _disposed; + internal RequestQueue(string requestQueueName, string urlPrefix, ILogger logger, bool receiver) + : this(urlGroup: null, requestQueueName, RequestQueueMode.Attach, logger, receiver) + { + try + { + UrlGroup = new UrlGroup(this, UrlPrefix.Create(urlPrefix)); + } + catch + { + Dispose(); + throw; + } + } + internal RequestQueue(UrlGroup urlGroup, string requestQueueName, RequestQueueMode mode, ILogger logger) + : this(urlGroup, requestQueueName, mode, logger, false) + { } + + private RequestQueue(UrlGroup urlGroup, string requestQueueName, RequestQueueMode mode, ILogger logger, bool receiver) { _mode = mode; - _urlGroup = urlGroup; + UrlGroup = urlGroup; _logger = logger; var flags = HttpApiTypes.HTTP_CREATE_REQUEST_QUEUE_FLAG.None; Created = true; + if (_mode == RequestQueueMode.Attach) { flags = HttpApiTypes.HTTP_CREATE_REQUEST_QUEUE_FLAG.OpenExisting; Created = false; + if (receiver) + { + flags |= HttpApiTypes.HTTP_CREATE_REQUEST_QUEUE_FLAG.Delegation; + } } var statusCode = HttpApi.HttpCreateRequestQueue( @@ -54,7 +76,7 @@ internal RequestQueue(UrlGroup urlGroup, string requestQueueName, RequestQueueMo out requestQueueHandle); } - if (flags == HttpApiTypes.HTTP_CREATE_REQUEST_QUEUE_FLAG.OpenExisting && statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_FILE_NOT_FOUND) + if (flags.HasFlag(HttpApiTypes.HTTP_CREATE_REQUEST_QUEUE_FLAG.OpenExisting) && statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_FILE_NOT_FOUND) { throw new HttpSysException((int)statusCode, $"Failed to attach to the given request queue '{requestQueueName}', the queue could not be found."); } @@ -95,6 +117,8 @@ internal RequestQueue(UrlGroup urlGroup, string requestQueueName, RequestQueueMo internal SafeHandle Handle { get; } internal ThreadPoolBoundHandle BoundHandle { get; } + internal UrlGroup UrlGroup { get; } + internal unsafe void AttachToUrlGroup() { Debug.Assert(Created); @@ -108,7 +132,7 @@ internal unsafe void AttachToUrlGroup() var infoptr = new IntPtr(&info); - _urlGroup.SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty, + UrlGroup.SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty, infoptr, (uint)BindingInfoSize); } @@ -128,7 +152,7 @@ internal unsafe void DetachFromUrlGroup() var infoptr = new IntPtr(&info); - _urlGroup.SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty, + UrlGroup.SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty, infoptr, (uint)BindingInfoSize, throwOnError: false); } diff --git a/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs b/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs index 081c2d7e1591..de348d3786ee 100644 --- a/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs +++ b/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs @@ -13,6 +13,8 @@ internal class UrlGroup : IDisposable { private static readonly int QosInfoSize = Marshal.SizeOf(); + private static readonly int RequestPropertyInfoSize = + Marshal.SizeOf(); private ServerSession _serverSession; private ILogger _logger; @@ -36,6 +38,21 @@ internal unsafe UrlGroup(ServerSession serverSession, ILogger logger) Id = urlGroupId; } + internal unsafe UrlGroup(RequestQueue requestQueue, UrlPrefix url) + { + ulong urlGroupId = 0; + var statusCode = HttpApi.HttpFindUrlGroupId( + url.FullPrefix, requestQueue.Handle, &urlGroupId); + + if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) + { + throw new HttpSysException((int)statusCode); + } + + Debug.Assert(urlGroupId != 0, "Invalid id returned by HttpCreateUrlGroup"); + Id = urlGroupId; + } + internal ulong Id { get; private set; } internal unsafe void SetMaxConnections(long maxConnections) @@ -51,6 +68,15 @@ internal unsafe void SetMaxConnections(long maxConnections) SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerQosProperty, new IntPtr(&qosSettings), (uint)QosInfoSize); } + internal unsafe void SetDelegationProperty(RequestQueue destination) + { + var propertyInfo = new HttpApiTypes.HTTP_BINDING_INFO(); + propertyInfo.Flags = HttpApiTypes.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT; + propertyInfo.RequestQueueHandle = destination.Handle.DangerousGetHandle(); + + SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerDelegationProperty, new IntPtr(&propertyInfo), (uint)RequestPropertyInfoSize); + } + internal void SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY property, IntPtr info, uint infosize, bool throwOnError = true) { Debug.Assert(info != IntPtr.Zero, "SetUrlGroupProperty called with invalid pointer"); diff --git a/src/Servers/HttpSys/src/RequestProcessing/Request.cs b/src/Servers/HttpSys/src/RequestProcessing/Request.cs index 84fb0173933b..494f3e5d6deb 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/Request.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/Request.cs @@ -61,43 +61,40 @@ internal Request(RequestContext requestContext, NativeRequestContext nativeReque var rawUrlInBytes = _nativeRequestContext.GetRawUrlInBytes(); var originalPath = RequestUriBuilder.DecodeAndUnescapePath(rawUrlInBytes); + PathBase = string.Empty; + Path = originalPath; + // 'OPTIONS * HTTP/1.1' if (KnownMethod == HttpApiTypes.HTTP_VERB.HttpVerbOPTIONS && string.Equals(RawUrl, "*", StringComparison.Ordinal)) { PathBase = string.Empty; Path = string.Empty; } - else if (requestContext.Server.RequestQueue.Created) + else { var prefix = requestContext.Server.Options.UrlPrefixes.GetPrefix((int)nativeRequestContext.UrlContext); - - if (originalPath.Length == prefix.PathWithoutTrailingSlash.Length) - { - // They matched exactly except for the trailing slash. - PathBase = originalPath; - Path = string.Empty; - } - else + // Prefix may be null if the requested has been transfered to our queue + if (!(prefix is null)) { - // url: /base/path, prefix: /base/, base: /base, path: /path - // url: /, prefix: /, base: , path: / - PathBase = originalPath.Substring(0, prefix.PathWithoutTrailingSlash.Length); // Preserve the user input casing - Path = originalPath.Substring(prefix.PathWithoutTrailingSlash.Length); + if (originalPath.Length == prefix.PathWithoutTrailingSlash.Length) + { + // They matched exactly except for the trailing slash. + PathBase = originalPath; + Path = string.Empty; + } + else + { + // url: /base/path, prefix: /base/, base: /base, path: /path + // url: /, prefix: /, base: , path: / + PathBase = originalPath.Substring(0, prefix.PathWithoutTrailingSlash.Length); // Preserve the user input casing + Path = originalPath.Substring(prefix.PathWithoutTrailingSlash.Length); + } } - } - else - { - // When attaching to an existing queue, the UrlContext hint may not match our configuration. Search manualy. - if (requestContext.Server.Options.UrlPrefixes.TryMatchLongestPrefix(IsHttps, cookedUrl.GetHost(), originalPath, out var pathBase, out var path)) + else if (requestContext.Server.Options.UrlPrefixes.TryMatchLongestPrefix(IsHttps, cookedUrl.GetHost(), originalPath, out var pathBase, out var path)) { PathBase = pathBase; Path = path; } - else - { - PathBase = string.Empty; - Path = originalPath; - } } ProtocolVersion = _nativeRequestContext.GetVersion(); @@ -350,6 +347,8 @@ public X509Certificate2 ClientCertificate } } + public bool CanDelegate => !(HasRequestBodyStarted || RequestContext.Response.HasStarted); + // Populates the client certificate. The result may be null if there is no client cert. // TODO: Does it make sense for this to be invoked multiple times (e.g. renegotiate)? Client and server code appear to // enable this, but it's unclear what Http.Sys would do. diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs index 997339ac1458..cc7cee0df359 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs @@ -2,8 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Concurrent; using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices; using System.Security.Authentication.ExtendedProtection; using System.Security.Principal; using System.Threading; @@ -17,7 +19,6 @@ namespace Microsoft.AspNetCore.Server.HttpSys internal sealed class RequestContext : IDisposable, IThreadPoolWorkItem { private static readonly Action AbortDelegate = Abort; - private NativeRequestContext _memoryBlob; private CancellationTokenSource _requestAbortSource; private CancellationToken? _disconnectToken; @@ -322,5 +323,45 @@ private void SetFatalResponse(int status) Response.ContentLength = 0; Dispose(); } + + internal unsafe void Delegate(DelegationRule destination) + { + if (Request.HasRequestBodyStarted) + { + throw new InvalidOperationException("This request cannot be delegated, the request body has already started."); + } + if (Response.HasStarted) + { + throw new InvalidOperationException("This request cannot be delegated, the response has already started."); + } + + var source = Server.RequestQueue; + + uint statusCode; + + fixed (char* uriPointer = destination.UrlPrefix) + { + var property = new HttpApiTypes.HTTP_DELEGATE_REQUEST_PROPERTY_INFO() + { + ProperyId = HttpApiTypes.HTTP_DELEGATE_REQUEST_PROPERTY_ID.DelegateRequestDelegateUrlProperty, + PropertyInfo = (IntPtr)uriPointer, + PropertyInfoLength = (uint)System.Text.Encoding.Unicode.GetByteCount(destination.UrlPrefix) + }; + + statusCode = HttpApi.HttpDelegateRequestEx(source.Handle, + destination.Queue.Handle, + Request.RequestId, + destination.Queue.UrlGroup.Id, + propertyInfoSetSize: 1, + &property); + } + + if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) + { + throw new HttpSysException((int)statusCode); + } + + Response.MarkDelegated(); + } } } diff --git a/src/Servers/HttpSys/src/RequestProcessing/Response.cs b/src/Servers/HttpSys/src/RequestProcessing/Response.cs index 3f8dd86f7e7a..a6fc52bd926a 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/Response.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/Response.cs @@ -730,6 +730,12 @@ internal void SendOpaqueUpgrade() } } + internal void MarkDelegated() + { + Abort(); + _nativeStream?.MarkDelegated(); + } + internal void CancelLastWrite() { _nativeStream?.CancelLastWrite(); diff --git a/src/Servers/HttpSys/src/RequestProcessing/ResponseBody.cs b/src/Servers/HttpSys/src/RequestProcessing/ResponseBody.cs index 6b181cc312cc..2e59530a9ca8 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/ResponseBody.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/ResponseBody.cs @@ -106,6 +106,11 @@ public override void Flush() FlushInternal(endOfRequest: false); } + public void MarkDelegated() + { + _skipWrites = true; + } + // We never expect endOfRequest and data at the same time private unsafe void FlushInternal(bool endOfRequest, ArraySegment data = new ArraySegment()) { diff --git a/src/Servers/HttpSys/src/ServerDelegationPropertyFeature.cs b/src/Servers/HttpSys/src/ServerDelegationPropertyFeature.cs new file mode 100644 index 000000000000..d9066a3dd52f --- /dev/null +++ b/src/Servers/HttpSys/src/ServerDelegationPropertyFeature.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.HttpSys +{ + internal class ServerDelegationPropertyFeature : IServerDelegationFeature + { + private readonly ILogger _logger; + private readonly RequestQueue _queue; + + public ServerDelegationPropertyFeature(RequestQueue queue, ILogger logger) + { + _queue = queue; + _logger = logger; + } + + public DelegationRule CreateDelegationRule(string queueName, string uri) + { + var rule = new DelegationRule(queueName, uri, _logger); + _queue.UrlGroup.SetDelegationProperty(rule.Queue); + return rule; + } + } +} diff --git a/src/Servers/HttpSys/src/StandardFeatureCollection.cs b/src/Servers/HttpSys/src/StandardFeatureCollection.cs index 304b39b07003..c8705b2ee6d2 100644 --- a/src/Servers/HttpSys/src/StandardFeatureCollection.cs +++ b/src/Servers/HttpSys/src/StandardFeatureCollection.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features.Authentication; +using Microsoft.AspNetCore.HttpSys.Internal; namespace Microsoft.AspNetCore.Server.HttpSys { @@ -44,6 +45,11 @@ static StandardFeatureCollection() // Win8+ _featureFuncLookup[typeof(ITlsHandshakeFeature)] = ctx => ctx.GetTlsHandshakeFeature(); } + + if (HttpApi.IsFeatureSupported(HttpApiTypes.HTTP_FEATURE_ID.HttpFeatureDelegateEx)) + { + _featureFuncLookup[typeof(IHttpSysRequestDelegationFeature)] = _identityFunc; + } } public StandardFeatureCollection(FeatureContext featureContext) diff --git a/src/Servers/HttpSys/test/FunctionalTests/DelegateSupportedConditionAttribute.cs b/src/Servers/HttpSys/test/FunctionalTests/DelegateSupportedConditionAttribute.cs new file mode 100644 index 000000000000..b6267657e4c1 --- /dev/null +++ b/src/Servers/HttpSys/test/FunctionalTests/DelegateSupportedConditionAttribute.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing; +using static Microsoft.AspNetCore.HttpSys.Internal.HttpApiTypes; + +namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class DelegateSupportedConditionAttribute : Attribute, ITestCondition + { + private readonly bool _isSupported; + public DelegateSupportedConditionAttribute(bool isSupported) => _isSupported = isSupported; + + private readonly Lazy _isDelegateSupported = new Lazy(CanDelegate); + public bool IsMet => (_isDelegateSupported.Value == _isSupported); + + public string SkipReason => $"Http.Sys does {(_isSupported ? "not" : "")} support delegating requests"; + + private static bool CanDelegate() + { + return HttpApi.IsFeatureSupported(HTTP_FEATURE_ID.HttpFeatureDelegateEx); + } + } +} diff --git a/src/Servers/HttpSys/test/FunctionalTests/DelegateTests.cs b/src/Servers/HttpSys/test/FunctionalTests/DelegateTests.cs new file mode 100644 index 000000000000..f94f0bf0be48 --- /dev/null +++ b/src/Servers/HttpSys/test/FunctionalTests/DelegateTests.cs @@ -0,0 +1,179 @@ +// 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.IO; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests +{ + public class DelegateTests + { + private static readonly string _expectedResponseString = "Hello from delegatee"; + + [ConditionalFact] + [DelegateSupportedCondition(true)] + public async Task DelegateRequestTest() + { + var queueName = Guid.NewGuid().ToString(); + using var receiver = Utilities.CreateHttpServer(out var receiverAddress, async httpContext => + { + await httpContext.Response.WriteAsync(_expectedResponseString); + }, + options => + { + options.RequestQueueName = queueName; + }); + + DelegationRule destination = default; + + using var delegator = Utilities.CreateHttpServer(out var delegatorAddress, httpContext => + { + var delegateFeature = httpContext.Features.Get(); + delegateFeature.DelegateRequest(destination); + return Task.CompletedTask; + }); + + var delegationProperty = delegator.Features.Get(); + destination = delegationProperty.CreateDelegationRule(queueName, receiverAddress); + + var responseString = await SendRequestAsync(delegatorAddress); + Assert.Equal(_expectedResponseString, responseString); + destination?.Dispose(); + } + + [ConditionalFact] + [DelegateSupportedCondition(true)] + public async Task DelegateAfterWriteToResponseBodyShouldThrowTest() + { + var queueName = Guid.NewGuid().ToString(); + using var receiver = Utilities.CreateHttpServer(out var receiverAddress, httpContext => + { + httpContext.Response.StatusCode = StatusCodes.Status418ImATeapot; + return Task.CompletedTask; + }, + options => + { + options.RequestQueueName = queueName; + }); + + DelegationRule destination = default; + + using var delegator = Utilities.CreateHttpServer(out var delegatorAddress, async httpContext => + { + await httpContext.Response.WriteAsync(_expectedResponseString); + var delegateFeature = httpContext.Features.Get(); + Assert.False(delegateFeature.CanDelegate); + Assert.Throws(() => delegateFeature.DelegateRequest(destination)); + }); + + var delegationProperty = delegator.Features.Get(); + destination = delegationProperty.CreateDelegationRule(queueName, receiverAddress); + + var responseString = await SendRequestAsync(delegatorAddress); + Assert.Equal(_expectedResponseString, responseString); + destination?.Dispose(); + } + + [ConditionalFact] + [DelegateSupportedCondition(true)] + public async Task WriteToBodyAfterDelegateShouldNoOp() + { + var queueName = Guid.NewGuid().ToString(); + using var receiver = Utilities.CreateHttpServer(out var receiverAddress, async httpContext => + { + await httpContext.Response.WriteAsync(_expectedResponseString); + }, + options => + { + options.RequestQueueName = queueName; + }); + + DelegationRule destination = default; + + using var delegator = Utilities.CreateHttpServer(out var delegatorAddress, httpContext => + { + var delegateFeature = httpContext.Features.Get(); + delegateFeature.DelegateRequest(destination); + Assert.False(delegateFeature.CanDelegate); + httpContext.Response.WriteAsync(_expectedResponseString); + return Task.CompletedTask; + }); + + var delegationProperty = delegator.Features.Get(); + destination = delegationProperty.CreateDelegationRule(queueName, receiverAddress); + + var responseString = await SendRequestAsync(delegatorAddress); + Assert.Equal(_expectedResponseString, responseString); + destination?.Dispose(); + } + + [ConditionalFact] + [DelegateSupportedCondition(true)] + public async Task DelegateAfterRequestBodyReadShouldThrow() + { + var queueName = Guid.NewGuid().ToString(); + using var receiver = Utilities.CreateHttpServer(out var receiverAddress, httpContext => + { + httpContext.Response.StatusCode = StatusCodes.Status418ImATeapot; + return Task.CompletedTask; + }, + options => + { + options.RequestQueueName = queueName; + }); + + DelegationRule destination = default; + + using var delegator = Utilities.CreateHttpServer(out var delegatorAddress, async httpContext => + { + var memoryStream = new MemoryStream(); + await httpContext.Request.Body.CopyToAsync(memoryStream); + var delegateFeature = httpContext.Features.Get(); + Assert.Throws(() => delegateFeature.DelegateRequest(destination)); + }); + + var delegationProperty = delegator.Features.Get(); + destination = delegationProperty.CreateDelegationRule(queueName, receiverAddress); + + _ = await SendRequestWithBodyAsync(delegatorAddress); + destination?.Dispose(); + } + + [ConditionalFact] + [DelegateSupportedCondition(false)] + public async Task DelegationFeaturesAreNull() + { + using var delegator = Utilities.CreateHttpServer(out var delegatorAddress, httpContext => + { + var delegateFeature = httpContext.Features.Get(); + Assert.Null(delegateFeature); + return Task.CompletedTask; + }); + + var delegationProperty = delegator.Features.Get(); + Assert.Null(delegationProperty); + + _ = await SendRequestAsync(delegatorAddress); + } + + private async Task SendRequestAsync(string uri) + { + using var client = new HttpClient(); + return await client.GetStringAsync(uri); + } + + private async Task SendRequestWithBodyAsync(string uri) + { + using var client = new HttpClient(); + var content = new StringContent("Sample request body"); + var response = await client.PostAsync(uri, content); + response.EnsureSuccessStatusCode(); + return await response.Content.ReadAsStringAsync(); + } + } +} diff --git a/src/Shared/HttpSys/NativeInterop/HttpApiTypes.cs b/src/Shared/HttpSys/NativeInterop/HttpApiTypes.cs index 2e69d7bc4002..706fbd3d3256 100644 --- a/src/Shared/HttpSys/NativeInterop/HttpApiTypes.cs +++ b/src/Shared/HttpSys/NativeInterop/HttpApiTypes.cs @@ -32,6 +32,7 @@ internal enum HTTP_SERVER_PROPERTY HttpServerListenEndpointProperty, HttpServerChannelBindProperty, HttpServerProtectionLevelProperty, + HttpServerDelegationProperty = 16 } // Currently only one request info type is supported but the enum is for future extensibility. @@ -71,6 +72,28 @@ internal enum HTTP_TIMEOUT_TYPE MinSendRate, } + internal enum HTTP_DELEGATE_REQUEST_PROPERTY_ID : uint + { + DelegateRequestReservedProperty, + DelegateRequestDelegateUrlProperty + } + + internal enum HTTP_FEATURE_ID + { + HttpFeatureUnknown = 0, + HttpFeatureResponseTrailers = 1, + HttpFeatureApiTimings = 2, + HttpFeatureDelegateEx = 3, + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + internal struct HTTP_DELEGATE_REQUEST_PROPERTY_INFO + { + internal HTTP_DELEGATE_REQUEST_PROPERTY_ID ProperyId; + internal uint PropertyInfoLength; + internal IntPtr PropertyInfo; + } + internal struct HTTP_REQUEST_PROPERTY_STREAM_ERROR { internal uint ErrorCode; @@ -651,6 +674,7 @@ internal enum HTTP_CREATE_REQUEST_QUEUE_FLAG : uint OpenExisting = 1, // The handle to the request queue created using this flag cannot be used to perform I/O operations. This flag can be set only when the request queue handle is created. Controller = 2, + Delegation = 8 } internal static class HTTP_RESPONSE_HEADER_ID From dd43757bfe015692112aa43725483c399ffe054b Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2020 18:38:38 +0000 Subject: [PATCH 6/8] Update dependencies from https://github.com/dotnet/efcore build 20200819.5 (#25040) [release/5.0] Update dependencies from dotnet/efcore - Updates: - Microsoft.EntityFrameworkCore.Tools: from 5.0.0-rc.1.20417.2 to 5.0.0-rc.1.20419.5 - Microsoft.EntityFrameworkCore.SqlServer: from 5.0.0-rc.1.20417.2 to 5.0.0-rc.1.20419.5 - dotnet-ef: from 5.0.0-rc.1.20417.2 to 5.0.0-rc.1.20419.5 - Microsoft.EntityFrameworkCore: from 5.0.0-rc.1.20417.2 to 5.0.0-rc.1.20419.5 - Microsoft.EntityFrameworkCore.Design: from 5.0.0-rc.1.20417.2 to 5.0.0-rc.1.20419.5 - Microsoft.EntityFrameworkCore.Relational: from 5.0.0-rc.1.20417.2 to 5.0.0-rc.1.20419.5 - Microsoft.EntityFrameworkCore.Sqlite: from 5.0.0-rc.1.20417.2 to 5.0.0-rc.1.20419.5 - Microsoft.EntityFrameworkCore.InMemory: from 5.0.0-rc.1.20417.2 to 5.0.0-rc.1.20419.5 --- eng/Version.Details.xml | 32 ++++++++++++++++---------------- eng/Versions.props | 16 ++++++++-------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 87f35b297962..3113cbd68c7a 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -13,37 +13,37 @@ https://github.com/dotnet/blazor cc449601d638ffaab58ae9487f0fd010bb178a12 - + https://github.com/dotnet/efcore - 5099d918192f5df031e1ff5e3beea9cb361c605a + 897dc03d976fa2c115c81c867c074c6cf5ece282 - + https://github.com/dotnet/efcore - 5099d918192f5df031e1ff5e3beea9cb361c605a + 897dc03d976fa2c115c81c867c074c6cf5ece282 - + https://github.com/dotnet/efcore - 5099d918192f5df031e1ff5e3beea9cb361c605a + 897dc03d976fa2c115c81c867c074c6cf5ece282 - + https://github.com/dotnet/efcore - 5099d918192f5df031e1ff5e3beea9cb361c605a + 897dc03d976fa2c115c81c867c074c6cf5ece282 - + https://github.com/dotnet/efcore - 5099d918192f5df031e1ff5e3beea9cb361c605a + 897dc03d976fa2c115c81c867c074c6cf5ece282 - + https://github.com/dotnet/efcore - 5099d918192f5df031e1ff5e3beea9cb361c605a + 897dc03d976fa2c115c81c867c074c6cf5ece282 - + https://github.com/dotnet/efcore - 5099d918192f5df031e1ff5e3beea9cb361c605a + 897dc03d976fa2c115c81c867c074c6cf5ece282 - + https://github.com/dotnet/efcore - 5099d918192f5df031e1ff5e3beea9cb361c605a + 897dc03d976fa2c115c81c867c074c6cf5ece282 https://github.com/dotnet/runtime diff --git a/eng/Versions.props b/eng/Versions.props index fabfae40a79d..ac700a988988 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -133,14 +133,14 @@ 3.2.0 - 5.0.0-rc.1.20417.2 - 5.0.0-rc.1.20417.2 - 5.0.0-rc.1.20417.2 - 5.0.0-rc.1.20417.2 - 5.0.0-rc.1.20417.2 - 5.0.0-rc.1.20417.2 - 5.0.0-rc.1.20417.2 - 5.0.0-rc.1.20417.2 + 5.0.0-rc.1.20419.5 + 5.0.0-rc.1.20419.5 + 5.0.0-rc.1.20419.5 + 5.0.0-rc.1.20419.5 + 5.0.0-rc.1.20419.5 + 5.0.0-rc.1.20419.5 + 5.0.0-rc.1.20419.5 + 5.0.0-rc.1.20419.5