From 35b5f091d81dffbc8445bf6666f05954d02ff3cf Mon Sep 17 00:00:00 2001 From: Stafford Williams Date: Thu, 29 Aug 2019 09:13:44 +1000 Subject: [PATCH 01/31] Crankier: server docs (#13242) --- .../perf/benchmarkapps/Crankier/Readme.md | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Readme.md b/src/SignalR/perf/benchmarkapps/Crankier/Readme.md index 20f56cc720..7b0a95fbe9 100644 --- a/src/SignalR/perf/benchmarkapps/Crankier/Readme.md +++ b/src/SignalR/perf/benchmarkapps/Crankier/Readme.md @@ -4,6 +4,24 @@ Load testing for ASP.NET Core SignalR ## Commands +### server + +The `server` command runs a web host exposing a single SignalR `Hub` endpoint on `/echo`. After the first client connection, the server will periodically write concurrent connection information to the console. + +``` +> dotnet run -- help server + +Usage: server [options] + +Options: + --log The LogLevel to use. +``` + +Notes: + +* `LOG_LEVEL` switches internal logging only, not concurrent connection information, and defaults to `LogLevel.None`. Use this option to control Kestrel / SignalR Warnings & Errors being logged to console. + + ### local The `local` command launches a set of local worker clients to establish connections to your SignalR server. @@ -31,13 +49,19 @@ Notes: #### Examples -Attempt to make 10,000 connections to the `echo` hub using WebSockets and 10 workers: +Run the server: + +``` +dotnet run -- server +``` + +Attempt to make 10,000 connections to the server using WebSockets and 10 workers: ``` dotnet run -- local --target-url https://localhost:5001/echo --workers 10 ``` -Attempt to make 5,000 connections to the `echo` hub using Long Polling +Attempt to make 5,000 connections to the server using Long Polling ``` dotnet run -- local --target-url https://localhost:5001/echo --connections 5000 --transport LongPolling From 806fef3a278c01f55ffd98a539ea6bd8ceeabf48 Mon Sep 17 00:00:00 2001 From: John Luo Date: Wed, 28 Aug 2019 19:36:53 -0700 Subject: [PATCH 02/31] Re-enable framework tests (#13043) --- src/Framework/test/Microsoft.AspNetCore.App.UnitTests.csproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Framework/test/Microsoft.AspNetCore.App.UnitTests.csproj b/src/Framework/test/Microsoft.AspNetCore.App.UnitTests.csproj index e90141a86e..f0edb2163b 100644 --- a/src/Framework/test/Microsoft.AspNetCore.App.UnitTests.csproj +++ b/src/Framework/test/Microsoft.AspNetCore.App.UnitTests.csproj @@ -5,8 +5,6 @@ Microsoft.AspNetCore false - - true From 725fa34cc801017294a805a6c0929992456de42a Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Thu, 29 Aug 2019 12:12:28 -0700 Subject: [PATCH 03/31] Deflake Kestrel low response rate tests (#13532) --- .../test/FunctionalTests/ResponseTests.cs | 289 ++++++++++++------ 1 file changed, 204 insertions(+), 85 deletions(-) diff --git a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs index 6e20f3e71a..770ac5a1be 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs @@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.AspNetCore.Server.Kestrel.Https.Internal; @@ -756,6 +757,176 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests }; testContext.InitializeHeartbeat(); + var dateHeaderValueManager = new DateHeaderValueManager(); + dateHeaderValueManager.OnHeartbeat(DateTimeOffset.MinValue); + testContext.DateHeaderValueManager = dateHeaderValueManager; + + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + + async Task App(HttpContext context) + { + context.RequestAborted.Register(() => + { + requestAborted = true; + }); + + for (var i = 0; i < chunkCount; i++) + { + await context.Response.BodyWriter.WriteAsync(new Memory(chunkData, 0, chunkData.Length), context.RequestAborted); + } + + appFuncCompleted.SetResult(null); + } + + using (var server = new TestServer(App, testContext, listenOptions)) + { + using (var connection = server.CreateConnection()) + { + // Close the connection with the last request so AssertStreamCompleted actually completes. + await connection.Send( + "GET / HTTP/1.1", + "Host:", + "", + ""); + + await connection.Receive( + "HTTP/1.1 200 OK", + $"Date: {dateHeaderValueManager.GetDateHeaderValues().String}"); + + // Make sure consuming a single chunk exceeds the 2 second timeout. + var targetBytesPerSecond = chunkSize / 4; + + // expectedBytes was determined by manual testing. A constant Date header is used, so this shouldn't change unless + // the response header writing logic or response body chunking logic itself changes. + await AssertBytesReceivedAtTargetRate(connection.Stream, expectedBytes: 33_553_537, targetBytesPerSecond); + await appFuncCompleted.Task.DefaultTimeout(); + + connection.ShutdownSend(); + await connection.WaitForConnectionClose(); + } + await server.StopAsync(); + } + + mockKestrelTrace.Verify(t => t.ResponseMinimumDataRateNotSatisfied(It.IsAny(), It.IsAny()), Times.Never()); + mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny()), Times.Once()); + Assert.False(requestAborted); + } + + [Fact] + [CollectDump] + public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLargeResponseHeaders() + { + var headerSize = 1024 * 1024; // 1 MB for each header value + var headerCount = 64; // 64 MB of headers per response + var requestCount = 4; // Minimum of 256 MB of total response headers + var headerValue = new string('a', headerSize); + var headerStringValues = new StringValues(Enumerable.Repeat(headerValue, headerCount).ToArray()); + + var requestAborted = false; + var mockKestrelTrace = new Mock(); + + var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) + { + ServerOptions = + { + Limits = + { + MinResponseDataRate = new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(2)) + } + } + }; + + testContext.InitializeHeartbeat(); + var dateHeaderValueManager = new DateHeaderValueManager(); + dateHeaderValueManager.OnHeartbeat(DateTimeOffset.MinValue); + testContext.DateHeaderValueManager = dateHeaderValueManager; + + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + + async Task App(HttpContext context) + { + context.RequestAborted.Register(() => + { + requestAborted = true; + }); + + context.Response.Headers[$"X-Custom-Header"] = headerStringValues; + context.Response.ContentLength = 0; + + await context.Response.BodyWriter.FlushAsync(); + } + + using (var server = new TestServer(App, testContext, listenOptions)) + { + using (var connection = server.CreateConnection()) + { + for (var i = 0; i < requestCount - 1; i++) + { + await connection.Send( + "GET / HTTP/1.1", + "Host:", + "", + ""); + } + + await connection.Send( + "GET / HTTP/1.1", + "Host:", + "", + ""); + + await connection.Receive( + "HTTP/1.1 200 OK", + $"Date: {dateHeaderValueManager.GetDateHeaderValues().String}"); + + var minResponseSize = headerSize * headerCount; + var minTotalOutputSize = requestCount * minResponseSize; + + // Make sure consuming a single set of response headers exceeds the 2 second timeout. + var targetBytesPerSecond = minResponseSize / 4; + + // expectedBytes was determined by manual testing. A constant Date header is used, so this shouldn't change unless + // the response header writing logic itself changes. + await AssertBytesReceivedAtTargetRate(connection.Stream, expectedBytes: 268_439_596, targetBytesPerSecond); + connection.ShutdownSend(); + await connection.WaitForConnectionClose(); + } + + await server.StopAsync(); + } + + mockKestrelTrace.Verify(t => t.ResponseMinimumDataRateNotSatisfied(It.IsAny(), It.IsAny()), Times.Never()); + mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny()), Times.Once()); + Assert.False(requestAborted); + } + + [Fact] + [Flaky("https://github.com/aspnet/AspNetCore/issues/13219", FlakyOn.AzP.Linux, FlakyOn.Helix.All)] + public async Task ClientCanReceiveFullConnectionCloseResponseWithoutErrorAtALowDataRate() + { + var chunkSize = 64 * 128 * 1024; + var chunkCount = 4; + var chunkData = new byte[chunkSize]; + + var requestAborted = false; + var appFuncCompleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var mockKestrelTrace = new Mock(); + + var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) + { + ServerOptions = + { + Limits = + { + MinResponseDataRate = new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(2)) + } + } + }; + + testContext.InitializeHeartbeat(); + var dateHeaderValueManager = new DateHeaderValueManager(); + dateHeaderValueManager.OnHeartbeat(DateTimeOffset.MinValue); + testContext.DateHeaderValueManager = dateHeaderValueManager; var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); @@ -786,11 +957,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests "", ""); - var minTotalOutputSize = chunkCount * chunkSize; + await connection.Receive( + "HTTP/1.1 200 OK", + "Connection: close", + $"Date: {dateHeaderValueManager.GetDateHeaderValues().String}"); // Make sure consuming a single chunk exceeds the 2 second timeout. var targetBytesPerSecond = chunkSize / 4; - await AssertStreamCompleted(connection.Stream, minTotalOutputSize, targetBytesPerSecond); + + // expectedBytes was determined by manual testing. A constant Date header is used, so this shouldn't change unless + // the response header writing logic or response body chunking logic itself changes. + await AssertStreamCompletedAtTargetRate(connection.Stream, expectedBytes: 33_553_556, targetBytesPerSecond); await appFuncCompleted.Task.DefaultTimeout(); } await server.StopAsync(); @@ -801,87 +978,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests Assert.False(requestAborted); } - private bool ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLargeResponseHeadersRetryPredicate(Exception e) - => e is IOException && e.Message.Contains("Unable to read data from the transport connection: The I/O operation has been aborted because of either a thread exit or an application request"); - - [Fact] - [Flaky("https://github.com/dotnet/corefx/issues/30691", FlakyOn.AzP.Windows)] - [CollectDump] - public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLargeResponseHeaders() - { - var headerSize = 1024 * 1024; // 1 MB for each header value - var headerCount = 64; // 64 MB of headers per response - var requestCount = 4; // Minimum of 256 MB of total response headers - var headerValue = new string('a', headerSize); - var headerStringValues = new StringValues(Enumerable.Repeat(headerValue, headerCount).ToArray()); - - var requestAborted = false; - var mockKestrelTrace = new Mock(); - - var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) - { - ServerOptions = - { - Limits = - { - MinResponseDataRate = new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(2)) - } - } - }; - - testContext.InitializeHeartbeat(); - - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); - - async Task App(HttpContext context) - { - context.RequestAborted.Register(() => - { - requestAborted = true; - }); - - context.Response.Headers[$"X-Custom-Header"] = headerStringValues; - context.Response.ContentLength = 0; - - await context.Response.BodyWriter.FlushAsync(); - } - - using (var server = new TestServer(App, testContext, listenOptions)) - { - using (var connection = server.CreateConnection()) - { - for (var i = 0; i < requestCount - 1; i++) - { - await connection.Send( - "GET / HTTP/1.1", - "Host:", - "", - ""); - } - - // Close the connection with the last request so AssertStreamCompleted actually completes. - await connection.Send( - "GET / HTTP/1.1", - "Host:", - "Connection: close", - "", - ""); - - var responseSize = headerSize * headerCount; - var minTotalOutputSize = requestCount * responseSize; - - // Make sure consuming a single set of response headers exceeds the 2 second timeout. - var targetBytesPerSecond = responseSize / 4; - await AssertStreamCompleted(connection.Stream, minTotalOutputSize, targetBytesPerSecond); - } - await server.StopAsync(); - } - - mockKestrelTrace.Verify(t => t.ResponseMinimumDataRateNotSatisfied(It.IsAny(), It.IsAny()), Times.Never()); - mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny()), Times.Once()); - Assert.False(requestAborted); - } - private async Task AssertStreamAborted(Stream stream, int totalBytes) { var receiveBuffer = new byte[64 * 1024]; @@ -909,7 +1005,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests Assert.True(totalReceived < totalBytes, $"{nameof(AssertStreamAborted)} Stream completed successfully."); } - private async Task AssertStreamCompleted(Stream stream, long minimumBytes, int targetBytesPerSecond) + private async Task AssertBytesReceivedAtTargetRate(Stream stream, int expectedBytes, int targetBytesPerSecond) + { + var receiveBuffer = new byte[64 * 1024]; + var totalReceived = 0; + var startTime = DateTimeOffset.UtcNow; + + do + { + var received = await stream.ReadAsync(receiveBuffer, 0, Math.Min(receiveBuffer.Length, expectedBytes - totalReceived)); + + Assert.NotEqual(0, received); + + totalReceived += received; + + var expectedTimeElapsed = TimeSpan.FromSeconds(totalReceived / targetBytesPerSecond); + var timeElapsed = DateTimeOffset.UtcNow - startTime; + if (timeElapsed < expectedTimeElapsed) + { + await Task.Delay(expectedTimeElapsed - timeElapsed); + } + } while (totalReceived < expectedBytes); + } + + private async Task AssertStreamCompletedAtTargetRate(Stream stream, long expectedBytes, int targetBytesPerSecond) { var receiveBuffer = new byte[64 * 1024]; var received = 0; @@ -929,7 +1048,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } while (received > 0); - Assert.True(totalReceived >= minimumBytes, $"{nameof(AssertStreamCompleted)} Stream aborted prematurely."); + Assert.Equal(expectedBytes, totalReceived); } public static TheoryData NullHeaderData From 9399f09b723f9316d94dc1257eee864667981a48 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 30 Aug 2019 18:31:32 +0100 Subject: [PATCH 04/31] BufferSegment use ArrayPool for over-sized allocs (#13495) --- .../Infrastructure/PipeWriterHelpers/BufferSegment.cs | 10 +++------- .../PipeWriterHelpers/ConcurrentPipeWriter.cs | 4 ++-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/BufferSegment.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/BufferSegment.cs index fdd8ac7367..a9bf8d9424 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/BufferSegment.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/BufferSegment.cs @@ -59,20 +59,16 @@ namespace System.IO.Pipelines AvailableMemory = arrayPoolBuffer; } - public void SetUnownedMemory(Memory memory) - { - AvailableMemory = memory; - } - public void ResetMemory() { if (_memoryOwner is IMemoryOwner owner) { owner.Dispose(); } - else if (_memoryOwner is byte[] array) + else { - ArrayPool.Shared.Return(array); + byte[] poolArray = (byte[])_memoryOwner; + ArrayPool.Shared.Return(poolArray); } // Order of below field clears is significant as it clears in a sequential order diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/ConcurrentPipeWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/ConcurrentPipeWriter.cs index 3ab051c8c4..4e09b0a8a4 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/ConcurrentPipeWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/ConcurrentPipeWriter.cs @@ -341,8 +341,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.PipeW } else { - // We can't use the pool so allocate an array - newSegment.SetUnownedMemory(new byte[sizeHint]); + // We can't use the recommended pool so use the ArrayPool + newSegment.SetOwnedMemory(ArrayPool.Shared.Rent(sizeHint)); } _tailMemory = newSegment.AvailableMemory; From 026b9df3e0526bae37fb85b18ccb69ec8d71bf30 Mon Sep 17 00:00:00 2001 From: dannyBies Date: Sat, 31 Aug 2019 10:23:32 +1200 Subject: [PATCH 05/31] Added .env files to the react project templates in order to run the CRA server independently. (#13525) * Add .env file to react projecttemplates in order to run the CRA server independently * Fix #6837 * Revert "Fix #6837" This reverts commit 2a035e67774058281c081a37867593e1a45900af. * Add .env file to react projecttemplates in order to run the CRA server independently * Fix #6837 * Revert "Fix #6837" This reverts commit 2a035e67774058281c081a37867593e1a45900af. * Add newline to end of file --- .../Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/.env | 1 + .../content/ReactRedux-CSharp/ClientApp/.env | 1 + 2 files changed, 2 insertions(+) create mode 100644 src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/.env create mode 100644 src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.env diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/.env b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/.env new file mode 100644 index 0000000000..6ce384e5ce --- /dev/null +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/.env @@ -0,0 +1 @@ +BROWSER=none diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.env b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.env new file mode 100644 index 0000000000..6ce384e5ce --- /dev/null +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.env @@ -0,0 +1 @@ +BROWSER=none From 1f6d235a5e7fd9427c96b7508610c5d3401a1571 Mon Sep 17 00:00:00 2001 From: Tom Kerkhove Date: Thu, 29 Aug 2019 19:32:52 +0200 Subject: [PATCH 06/31] Health Checks - EF health check should respect configured failureStatus Signed-off-by: Tom Kerkhove --- .../src/DbContextHealthCheck.cs | 6 ++-- .../test/DbContextHealthCheckTest.cs | 32 ++++++++++++++++--- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/Middleware/HealthChecks.EntityFrameworkCore/src/DbContextHealthCheck.cs b/src/Middleware/HealthChecks.EntityFrameworkCore/src/DbContextHealthCheck.cs index 7fa998f296..6481bd29d3 100644 --- a/src/Middleware/HealthChecks.EntityFrameworkCore/src/DbContextHealthCheck.cs +++ b/src/Middleware/HealthChecks.EntityFrameworkCore/src/DbContextHealthCheck.cs @@ -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,8 +49,8 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks { return HealthCheckResult.Healthy(); } - - return HealthCheckResult.Unhealthy(); + + return new HealthCheckResult(context.Registration.FailureStatus); } } } diff --git a/src/Middleware/HealthChecks.EntityFrameworkCore/test/DbContextHealthCheckTest.cs b/src/Middleware/HealthChecks.EntityFrameworkCore/test/DbContextHealthCheckTest.cs index fe5383c6f5..df22c3361f 100644 --- a/src/Middleware/HealthChecks.EntityFrameworkCore/test/DbContextHealthCheckTest.cs +++ b/src/Middleware/HealthChecks.EntityFrameworkCore/test/DbContextHealthCheckTest.cs @@ -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; @@ -61,7 +61,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks } [Fact] - public async Task CheckAsync_CustomTest_Degraded() + public async Task CheckAsync_CustomTestWithDegradedFailureStatusSpecified_Degraded() { // Arrange var services = CreateServices(async (c, ct) => @@ -78,12 +78,12 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks var result = await check.CheckHealthAsync(new HealthCheckContext() { Registration = registration, }); // Assert - Assert.Equal(HealthStatus.Unhealthy, result.Status); + Assert.Equal(HealthStatus.Degraded, result.Status); } } [Fact] - public async Task CheckAsync_CustomTest_Unhealthy() + public async Task CheckAsync_CustomTestWithUnhealthyFailureStatusSpecified_Unhealthy() { // Arrange var services = CreateServices(async (c, ct) => @@ -104,12 +104,34 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks } } + [Fact] + public async Task CheckAsync_CustomTestWithNoFailureStatusSpecified_Unhealthy() + { + // Arrange + var services = CreateServices(async (c, ct) => + { + return 0 < await c.Blogs.CountAsync(); + }, failureStatus: null); + + using (var scope = services.GetRequiredService().CreateScope()) + { + var registration = Assert.Single(services.GetRequiredService>().Value.Registrations); + var check = ActivatorUtilities.CreateInstance>(scope.ServiceProvider); + + // Act + var result = await check.CheckHealthAsync(new HealthCheckContext() { Registration = registration, }); + + // Assert + Assert.Equal(HealthStatus.Unhealthy, result.Status); + } + } + // used to ensure each test uses a unique in-memory database private static int _testDbCounter; private static IServiceProvider CreateServices( Func> testQuery = null, - HealthStatus failureStatus = HealthStatus.Unhealthy) + HealthStatus? failureStatus = HealthStatus.Unhealthy) { var serviceCollection = new ServiceCollection(); serviceCollection.AddDbContext(o => o.UseInMemoryDatabase("Test" + Interlocked.Increment(ref _testDbCounter))); From 88dbd1cff64d4d70c396a03c2a90a1ecdbfd3344 Mon Sep 17 00:00:00 2001 From: Kahbazi Date: Tue, 3 Sep 2019 22:44:42 +0430 Subject: [PATCH 07/31] Use Result instead of GetAwaiter().GetResult() (#13614) --- src/Middleware/CORS/src/Infrastructure/CorsMiddleware.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Middleware/CORS/src/Infrastructure/CorsMiddleware.cs b/src/Middleware/CORS/src/Infrastructure/CorsMiddleware.cs index ed3f743dfc..0b8ecc393e 100644 --- a/src/Middleware/CORS/src/Infrastructure/CorsMiddleware.cs +++ b/src/Middleware/CORS/src/Infrastructure/CorsMiddleware.cs @@ -181,7 +181,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure return InvokeCoreAwaited(context, policyTask); } - corsPolicy = policyTask.GetAwaiter().GetResult(); + corsPolicy = policyTask.Result; } return EvaluateAndApplyPolicy(context, corsPolicy); From e0ee04237af2fa58a525dbd63888894f62195f0a Mon Sep 17 00:00:00 2001 From: Mikael Mengistu Date: Tue, 3 Sep 2019 19:53:32 -0700 Subject: [PATCH 08/31] SignalR Negotiate protocol versioning (#13389) --- .../FunctionalTests/HubConnectionTests.cs | 65 +++++++++++++++++++ .../Client/test/FunctionalTests/Startup.cs | 10 +++ ...HttpConnectionTests.ConnectionLifecycle.cs | 2 +- .../HttpConnectionTests.Negotiate.cs | 65 +++++++++++++++---- .../test/UnitTests/TestHttpMessageHandler.cs | 5 +- .../src/HttpConnection.cs | 6 +- ...Core.Http.Connections.Common.netcoreapp.cs | 1 + ....Http.Connections.Common.netstandard2.0.cs | 1 + .../src/NegotiateProtocol.cs | 21 ++++++ .../src/NegotiationResponse.cs | 1 + ....AspNetCore.Http.Connections.netcoreapp.cs | 1 + .../src/HttpConnectionDispatcherOptions.cs | 6 ++ .../Internal/HttpConnectionDispatcher.Log.cs | 16 +++++ .../src/Internal/HttpConnectionDispatcher.cs | 42 +++++++++++- .../test/HttpConnectionDispatcherTests.cs | 58 +++++++++++++++++ .../test/NegotiateProtocolTests.cs | 23 ++++--- 16 files changed, 295 insertions(+), 28 deletions(-) diff --git a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs index 8198efd9cf..fbbc78a2ea 100644 --- a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs +++ b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs @@ -114,6 +114,71 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests } } + [Fact] + public async Task ServerRejectsClientWithOldProtocol() + { + bool ExpectedError(WriteContext writeContext) + { + return writeContext.LoggerName == typeof(HttpConnection).FullName && + writeContext.EventId.Name == "ErrorWithNegotiation"; + } + + var protocol = HubProtocols["json"]; + using (StartServer(out var server, ExpectedError)) + { + var connectionBuilder = new HubConnectionBuilder() + .WithLoggerFactory(LoggerFactory) + .WithUrl(server.Url + "/negotiateProtocolVersion12", HttpTransportType.LongPolling); + connectionBuilder.Services.AddSingleton(protocol); + + var connection = connectionBuilder.Build(); + + try + { + var ex = await Assert.ThrowsAnyAsync(() => connection.StartAsync()).OrTimeout(); + Assert.Equal("The client requested version '1', but the server does not support this version.", ex.Message); + } + catch (Exception ex) + { + LoggerFactory.CreateLogger().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName); + throw; + } + finally + { + await connection.DisposeAsync().OrTimeout(); + } + } + } + + [Fact] + public async Task ClientCanConnectToServerWithLowerMinimumProtocol() + { + var protocol = HubProtocols["json"]; + using (StartServer(out var server)) + { + var connectionBuilder = new HubConnectionBuilder() + .WithLoggerFactory(LoggerFactory) + .WithUrl(server.Url + "/negotiateProtocolVersionNegative", HttpTransportType.LongPolling); + connectionBuilder.Services.AddSingleton(protocol); + + var connection = connectionBuilder.Build(); + + try + { + await connection.StartAsync().OrTimeout(); + } + catch (Exception ex) + { + LoggerFactory.CreateLogger().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName); + throw; + } + finally + { + await connection.DisposeAsync().OrTimeout(); + } + } + } + [Theory] [MemberData(nameof(HubProtocolsAndTransportsAndHubPaths))] public async Task CanSendAndReceiveMessage(string protocolName, HttpTransportType transportType, string path) diff --git a/src/SignalR/clients/csharp/Client/test/FunctionalTests/Startup.cs b/src/SignalR/clients/csharp/Client/test/FunctionalTests/Startup.cs index 1d7dbd6718..4cbc35c510 100644 --- a/src/SignalR/clients/csharp/Client/test/FunctionalTests/Startup.cs +++ b/src/SignalR/clients/csharp/Client/test/FunctionalTests/Startup.cs @@ -69,6 +69,16 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests endpoints.MapHub("/default-nowebsockets", options => options.Transports = HttpTransportType.LongPolling | HttpTransportType.ServerSentEvents); + endpoints.MapHub("/negotiateProtocolVersion12", options => + { + options.MinimumProtocolVersion = 12; + }); + + endpoints.MapHub("/negotiateProtocolVersionNegative", options => + { + options.MinimumProtocolVersion = -1; + }); + endpoints.MapGet("/generateJwtToken", context => { return context.Response.WriteAsync(GenerateJwtToken()); diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.ConnectionLifecycle.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.ConnectionLifecycle.cs index fa95fbc83b..e04e82a2b5 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.ConnectionLifecycle.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.ConnectionLifecycle.cs @@ -359,7 +359,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests var httpHandler = new TestHttpMessageHandler(); var connectResponseTcs = new TaskCompletionSource(); - httpHandler.OnGet("/?id=00000000-0000-0000-0000-000000000000", async (_, __) => + httpHandler.OnGet("/?negotiateVersion=1&id=00000000-0000-0000-0000-000000000000", async (_, __) => { await connectResponseTcs.Task; return ResponseUtils.CreateResponse(HttpStatusCode.Accepted); diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Negotiate.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Negotiate.cs index 348e33cebf..47a16a48d9 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Negotiate.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Negotiate.cs @@ -50,12 +50,12 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests } [Theory] - [InlineData("http://fakeuri.org/", "http://fakeuri.org/negotiate")] - [InlineData("http://fakeuri.org/?q=1/0", "http://fakeuri.org/negotiate?q=1/0")] - [InlineData("http://fakeuri.org?q=1/0", "http://fakeuri.org/negotiate?q=1/0")] - [InlineData("http://fakeuri.org/endpoint", "http://fakeuri.org/endpoint/negotiate")] - [InlineData("http://fakeuri.org/endpoint/", "http://fakeuri.org/endpoint/negotiate")] - [InlineData("http://fakeuri.org/endpoint?q=1/0", "http://fakeuri.org/endpoint/negotiate?q=1/0")] + [InlineData("http://fakeuri.org/", "http://fakeuri.org/negotiate?negotiateVersion=1")] + [InlineData("http://fakeuri.org/?q=1/0", "http://fakeuri.org/negotiate?q=1/0&negotiateVersion=1")] + [InlineData("http://fakeuri.org?q=1/0", "http://fakeuri.org/negotiate?q=1/0&negotiateVersion=1")] + [InlineData("http://fakeuri.org/endpoint", "http://fakeuri.org/endpoint/negotiate?negotiateVersion=1")] + [InlineData("http://fakeuri.org/endpoint/", "http://fakeuri.org/endpoint/negotiate?negotiateVersion=1")] + [InlineData("http://fakeuri.org/endpoint?q=1/0", "http://fakeuri.org/endpoint/negotiate?q=1/0&negotiateVersion=1")] public async Task CorrectlyHandlesQueryStringWhenAppendingNegotiateToUrl(string requestedUrl, string expectedNegotiate) { var testHttpHandler = new TestHttpMessageHandler(autoNegotiate: false); @@ -119,6 +119,43 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests Assert.Equal("0rge0d00-0040-0030-0r00-000q00r00e00", connectionId); } + [Fact] + public async Task NegotiateCanHaveNewFields() + { + string connectionId = null; + + var testHttpHandler = new TestHttpMessageHandler(autoNegotiate: false); + testHttpHandler.OnNegotiate((request, cancellationToken) => ResponseUtils.CreateResponse(HttpStatusCode.OK, + JsonConvert.SerializeObject(new + { + connectionId = "0rge0d00-0040-0030-0r00-000q00r00e00", + availableTransports = new object[] + { + new + { + transport = "LongPolling", + transferFormats = new[] { "Text" } + }, + }, + newField = "ignore this", + }))); + testHttpHandler.OnLongPoll(cancellationToken => ResponseUtils.CreateResponse(HttpStatusCode.NoContent)); + testHttpHandler.OnLongPollDelete((token) => ResponseUtils.CreateResponse(HttpStatusCode.Accepted)); + + using (var noErrorScope = new VerifyNoErrorsScope()) + { + await WithConnectionAsync( + CreateConnection(testHttpHandler, loggerFactory: noErrorScope.LoggerFactory), + async (connection) => + { + await connection.StartAsync().OrTimeout(); + connectionId = connection.ConnectionId; + }); + } + + Assert.Equal("0rge0d00-0040-0030-0r00-000q00r00e00", connectionId); + } + [Fact] public async Task NegotiateThatReturnsUrlGetFollowed() { @@ -172,10 +209,10 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests }); } - Assert.Equal("http://fakeuri.org/negotiate", testHttpHandler.ReceivedRequests[0].RequestUri.ToString()); - Assert.Equal("https://another.domain.url/chat/negotiate", testHttpHandler.ReceivedRequests[1].RequestUri.ToString()); - Assert.Equal("https://another.domain.url/chat?id=0rge0d00-0040-0030-0r00-000q00r00e00", testHttpHandler.ReceivedRequests[2].RequestUri.ToString()); - Assert.Equal("https://another.domain.url/chat?id=0rge0d00-0040-0030-0r00-000q00r00e00", testHttpHandler.ReceivedRequests[3].RequestUri.ToString()); + Assert.Equal("http://fakeuri.org/negotiate?negotiateVersion=1", testHttpHandler.ReceivedRequests[0].RequestUri.ToString()); + Assert.Equal("https://another.domain.url/chat/negotiate?negotiateVersion=1", testHttpHandler.ReceivedRequests[1].RequestUri.ToString()); + Assert.Equal("https://another.domain.url/chat?negotiateVersion=1&id=0rge0d00-0040-0030-0r00-000q00r00e00", testHttpHandler.ReceivedRequests[2].RequestUri.ToString()); + Assert.Equal("https://another.domain.url/chat?negotiateVersion=1&id=0rge0d00-0040-0030-0r00-000q00r00e00", testHttpHandler.ReceivedRequests[3].RequestUri.ToString()); Assert.Equal(5, testHttpHandler.ReceivedRequests.Count); } @@ -278,10 +315,10 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests }); } - Assert.Equal("http://fakeuri.org/negotiate", testHttpHandler.ReceivedRequests[0].RequestUri.ToString()); - Assert.Equal("https://another.domain.url/chat/negotiate", testHttpHandler.ReceivedRequests[1].RequestUri.ToString()); - Assert.Equal("https://another.domain.url/chat?id=0rge0d00-0040-0030-0r00-000q00r00e00", testHttpHandler.ReceivedRequests[2].RequestUri.ToString()); - Assert.Equal("https://another.domain.url/chat?id=0rge0d00-0040-0030-0r00-000q00r00e00", testHttpHandler.ReceivedRequests[3].RequestUri.ToString()); + Assert.Equal("http://fakeuri.org/negotiate?negotiateVersion=1", testHttpHandler.ReceivedRequests[0].RequestUri.ToString()); + Assert.Equal("https://another.domain.url/chat/negotiate?negotiateVersion=1", testHttpHandler.ReceivedRequests[1].RequestUri.ToString()); + Assert.Equal("https://another.domain.url/chat?negotiateVersion=1&id=0rge0d00-0040-0030-0r00-000q00r00e00", testHttpHandler.ReceivedRequests[2].RequestUri.ToString()); + Assert.Equal("https://another.domain.url/chat?negotiateVersion=1&id=0rge0d00-0040-0030-0r00-000q00r00e00", testHttpHandler.ReceivedRequests[3].RequestUri.ToString()); // Delete request Assert.Equal(5, testHttpHandler.ReceivedRequests.Count); } diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/TestHttpMessageHandler.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/TestHttpMessageHandler.cs index 06d05da7f5..36596d3236 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/TestHttpMessageHandler.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/TestHttpMessageHandler.cs @@ -1,3 +1,6 @@ +// 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.Net; @@ -117,7 +120,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests }); testHttpMessageHandler.OnRequest((request, next, cancellationToken) => { - if (request.Method.Equals(HttpMethod.Delete) && request.RequestUri.PathAndQuery.StartsWith("/?id=")) + if (request.Method.Equals(HttpMethod.Delete) && request.RequestUri.PathAndQuery.Contains("&id=")) { deleteCts.Cancel(); return Task.FromResult(ResponseUtils.CreateResponse(HttpStatusCode.Accepted)); diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs index 7fde2079dc..77205aecf5 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs @@ -26,6 +26,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client // Not configurable on purpose, high enough that if we reach here, it's likely // a buggy server private static readonly int _maxRedirects = 100; + private static readonly int _protocolVersionNumber = 1; private static readonly Task _noAccessToken = Task.FromResult(null); private static readonly TimeSpan HttpClientTimeout = TimeSpan.FromSeconds(120); @@ -428,8 +429,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Client urlBuilder.Path += "/"; } urlBuilder.Path += "negotiate"; + var uri = Utils.AppendQueryString(urlBuilder.Uri, $"negotiateVersion={_protocolVersionNumber}"); - using (var request = new HttpRequestMessage(HttpMethod.Post, urlBuilder.Uri)) + using (var request = new HttpRequestMessage(HttpMethod.Post, uri)) { // Corefx changed the default version and High Sierra curlhandler tries to upgrade request request.Version = new Version(1, 1); @@ -466,7 +468,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client throw new FormatException("Invalid connection id."); } - return Utils.AppendQueryString(url, "id=" + connectionId); + return Utils.AppendQueryString(url, $"negotiateVersion={_protocolVersionNumber}&id=" + connectionId); } private async Task StartTransport(Uri connectUrl, HttpTransportType transportType, TransferFormat transferFormat, CancellationToken cancellationToken) diff --git a/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netcoreapp.cs b/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netcoreapp.cs index 27d206da33..fb58827e4b 100644 --- a/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netcoreapp.cs +++ b/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netcoreapp.cs @@ -36,5 +36,6 @@ namespace Microsoft.AspNetCore.Http.Connections public string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Url { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int Version { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } } diff --git a/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netstandard2.0.cs b/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netstandard2.0.cs index 27d206da33..fb58827e4b 100644 --- a/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netstandard2.0.cs +++ b/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netstandard2.0.cs @@ -36,5 +36,6 @@ namespace Microsoft.AspNetCore.Http.Connections public string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Url { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int Version { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } } diff --git a/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs b/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs index 93035f0613..1d4c4f28ae 100644 --- a/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs +++ b/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs @@ -27,6 +27,8 @@ namespace Microsoft.AspNetCore.Http.Connections private static JsonEncodedText TransferFormatsPropertyNameBytes = JsonEncodedText.Encode(TransferFormatsPropertyName); private const string ErrorPropertyName = "error"; private static JsonEncodedText ErrorPropertyNameBytes = JsonEncodedText.Encode(ErrorPropertyName); + private const string NegotiateVersionPropertyName = "negotiateVersion"; + private static JsonEncodedText NegotiateVersionPropertyNameBytes = JsonEncodedText.Encode(NegotiateVersionPropertyName); // Use C#7.3's ReadOnlySpan optimization for static data https://vcsjones.com/2019/02/01/csharp-readonly-span-bytes-static/ // Used to detect ASP.NET SignalR Server connection attempt @@ -41,6 +43,19 @@ namespace Microsoft.AspNetCore.Http.Connections var writer = reusableWriter.GetJsonWriter(); writer.WriteStartObject(); + // If we already have an error its due to a protocol version incompatibility. + // We can just write the error and complete the JSON object and return. + if (!string.IsNullOrEmpty(response.Error)) + { + writer.WriteString(ErrorPropertyNameBytes, response.Error); + writer.WriteEndObject(); + writer.Flush(); + Debug.Assert(writer.CurrentDepth == 0); + return; + } + + writer.WriteNumber(NegotiateVersionPropertyNameBytes, response.Version); + if (!string.IsNullOrEmpty(response.Url)) { writer.WriteString(UrlPropertyNameBytes, response.Url); @@ -116,6 +131,7 @@ namespace Microsoft.AspNetCore.Http.Connections string accessToken = null; List availableTransports = null; string error = null; + int version = 0; var completed = false; while (!completed && reader.CheckRead()) @@ -135,6 +151,10 @@ namespace Microsoft.AspNetCore.Http.Connections { connectionId = reader.ReadAsString(ConnectionIdPropertyName); } + else if (reader.ValueTextEquals(NegotiateVersionPropertyNameBytes.EncodedUtf8Bytes)) + { + version = reader.ReadAsInt32(NegotiateVersionPropertyName).GetValueOrDefault(); + } else if (reader.ValueTextEquals(AvailableTransportsPropertyNameBytes.EncodedUtf8Bytes)) { reader.CheckRead(); @@ -195,6 +215,7 @@ namespace Microsoft.AspNetCore.Http.Connections AccessToken = accessToken, AvailableTransports = availableTransports, Error = error, + Version = version }; } catch (Exception ex) diff --git a/src/SignalR/common/Http.Connections.Common/src/NegotiationResponse.cs b/src/SignalR/common/Http.Connections.Common/src/NegotiationResponse.cs index a01d2e637c..cd21b6cb26 100644 --- a/src/SignalR/common/Http.Connections.Common/src/NegotiationResponse.cs +++ b/src/SignalR/common/Http.Connections.Common/src/NegotiationResponse.cs @@ -10,6 +10,7 @@ namespace Microsoft.AspNetCore.Http.Connections public string Url { get; set; } public string AccessToken { get; set; } public string ConnectionId { get; set; } + public int Version { get; set; } public IList AvailableTransports { get; set; } public string Error { get; set; } } diff --git a/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.netcoreapp.cs b/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.netcoreapp.cs index 7810a4985d..5ee369727c 100644 --- a/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.netcoreapp.cs +++ b/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.netcoreapp.cs @@ -53,6 +53,7 @@ namespace Microsoft.AspNetCore.Http.Connections public long ApplicationMaxBufferSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.Collections.Generic.IList AuthorizationData { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public Microsoft.AspNetCore.Http.Connections.LongPollingOptions LongPolling { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public int MinimumProtocolVersion { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public long TransportMaxBufferSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Http.Connections.HttpTransportType Transports { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Http.Connections.WebSocketOptions WebSockets { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } diff --git a/src/SignalR/common/Http.Connections/src/HttpConnectionDispatcherOptions.cs b/src/SignalR/common/Http.Connections/src/HttpConnectionDispatcherOptions.cs index eff4ae76e4..e1f97d7183 100644 --- a/src/SignalR/common/Http.Connections/src/HttpConnectionDispatcherOptions.cs +++ b/src/SignalR/common/Http.Connections/src/HttpConnectionDispatcherOptions.cs @@ -57,5 +57,11 @@ namespace Microsoft.AspNetCore.Http.Connections /// Gets or sets the maximum buffer size of the application writer. /// public long ApplicationMaxBufferSize { get; set; } + + /// + /// Gets or sets the minimum protocol verison supported by the server. + /// The default value is 0, the lowest possible protocol version. + /// + public int MinimumProtocolVersion { get; set; } = 0; } } diff --git a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.Log.cs b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.Log.cs index af91f08af2..80f3d32800 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.Log.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.Log.cs @@ -52,6 +52,12 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal private static readonly Action _failedToReadHttpRequestBody = LoggerMessage.Define(LogLevel.Debug, new EventId(14, "FailedToReadHttpRequestBody"), "Connection {TransportConnectionId} failed to read the HTTP request body."); + private static readonly Action _negotiateProtocolVersionMismatch = + LoggerMessage.Define(LogLevel.Debug, new EventId(15, "NegotiateProtocolVersionMismatch"), "The client requested version '{clientProtocolVersion}', but the server does not support this version."); + + private static readonly Action _invalidNegotiateProtocolVersion = + LoggerMessage.Define(LogLevel.Debug, new EventId(16, "InvalidNegotiateProtocolVersion"), "The client requested an invalid protocol version '{queryStringVersionValue}'"); + public static void ConnectionDisposed(ILogger logger, string connectionId) { _connectionDisposed(logger, connectionId, null); @@ -121,6 +127,16 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal { _failedToReadHttpRequestBody(logger, connectionId, ex); } + + public static void NegotiateProtocolVersionMismatch(ILogger logger, int clientProtocolVersion) + { + _negotiateProtocolVersionMismatch(logger, clientProtocolVersion, null); + } + + public static void InvalidNegotiateProtocolVersion(ILogger logger, string requestedProtocolVersion) + { + _invalidNegotiateProtocolVersion(logger, requestedProtocolVersion, null); + } } } } diff --git a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs index bf82562a7b..983b1270f6 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs @@ -45,6 +45,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal private readonly HttpConnectionManager _manager; private readonly ILoggerFactory _loggerFactory; private readonly ILogger _logger; + private static readonly int _protocolVersion = 1; public HttpConnectionDispatcher(HttpConnectionManager manager, ILoggerFactory loggerFactory) { @@ -306,9 +307,48 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal } } - private static void WriteNegotiatePayload(IBufferWriter writer, string connectionId, HttpContext context, HttpConnectionDispatcherOptions options) + private void WriteNegotiatePayload(IBufferWriter writer, string connectionId, HttpContext context, HttpConnectionDispatcherOptions options) { var response = new NegotiationResponse(); + + if (context.Request.Query.TryGetValue("NegotiateVersion", out var queryStringVersion)) + { + // Set the negotiate response to the protocol we use. + var queryStringVersionValue = queryStringVersion.ToString(); + if (int.TryParse(queryStringVersionValue, out var clientProtocolVersion)) + { + if (clientProtocolVersion < options.MinimumProtocolVersion) + { + response.Error = $"The client requested version '{clientProtocolVersion}', but the server does not support this version."; + Log.NegotiateProtocolVersionMismatch(_logger, clientProtocolVersion); + NegotiateProtocol.WriteResponse(response, writer); + return; + } + else if (clientProtocolVersion > _protocolVersion) + { + response.Version = _protocolVersion; + } + else + { + response.Version = clientProtocolVersion; + } + } + else + { + response.Error = $"The client requested an invalid protocol version '{queryStringVersionValue}'"; + Log.InvalidNegotiateProtocolVersion(_logger, queryStringVersionValue); + NegotiateProtocol.WriteResponse(response, writer); + return; + } + } + else if (options.MinimumProtocolVersion > 0) + { + // NegotiateVersion wasn't parsed meaning the client requests version 0. + response.Error = $"The client requested version '0', but the server does not support this version."; + NegotiateProtocol.WriteResponse(response, writer); + return; + } + response.ConnectionId = connectionId; response.AvailableTransports = new List(); diff --git a/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs b/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs index 90fee0efac..1be53a1dcd 100644 --- a/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs +++ b/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs @@ -96,6 +96,64 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests } } + [Fact] + public async Task InvalidNegotiateProtocolVersionThrows() + { + using (StartVerifiableLog()) + { + var manager = CreateConnectionManager(LoggerFactory); + var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); + var context = new DefaultHttpContext(); + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddOptions(); + var ms = new MemoryStream(); + context.Request.Path = "/foo"; + context.Request.Method = "POST"; + context.Response.Body = ms; + context.Request.QueryString = new QueryString("?negotiateVersion=Invalid"); + + var options = new HttpConnectionDispatcherOptions { TransportMaxBufferSize = 4, ApplicationMaxBufferSize = 4 }; + await dispatcher.ExecuteNegotiateAsync(context, options); + var negotiateResponse = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(ms.ToArray())); + + var error = negotiateResponse.Value("error"); + Assert.Equal("The client requested an invalid protocol version 'Invalid'", error); + + var connectionId = negotiateResponse.Value("connectionId"); + Assert.Null(connectionId); + } + } + + [Fact] + public async Task NoNegotiateVersionInQueryStringThrowsWhenMinProtocolVersionIsSet() + { + using (StartVerifiableLog()) + { + var manager = CreateConnectionManager(LoggerFactory); + var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); + var context = new DefaultHttpContext(); + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddOptions(); + var ms = new MemoryStream(); + context.Request.Path = "/foo"; + context.Request.Method = "POST"; + context.Response.Body = ms; + context.Request.QueryString = new QueryString(""); + + var options = new HttpConnectionDispatcherOptions { TransportMaxBufferSize = 4, ApplicationMaxBufferSize = 4, MinimumProtocolVersion = 1 }; + await dispatcher.ExecuteNegotiateAsync(context, options); + var negotiateResponse = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(ms.ToArray())); + + var error = negotiateResponse.Value("error"); + Assert.Equal("The client requested version '0', but the server does not support this version.", error); + + var connectionId = negotiateResponse.Value("connectionId"); + Assert.Null(connectionId); + } + } + [Theory] [InlineData(HttpTransportType.LongPolling)] [InlineData(HttpTransportType.ServerSentEvents)] diff --git a/src/SignalR/common/Http.Connections/test/NegotiateProtocolTests.cs b/src/SignalR/common/Http.Connections/test/NegotiateProtocolTests.cs index ed6a1d1a2d..c7d274d0b3 100644 --- a/src/SignalR/common/Http.Connections/test/NegotiateProtocolTests.cs +++ b/src/SignalR/common/Http.Connections/test/NegotiateProtocolTests.cs @@ -13,13 +13,17 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests public class NegotiateProtocolTests { [Theory] - [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[]}", "123", new string[0], null, null)] - [InlineData("{\"connectionId\":\"\",\"availableTransports\":[]}", "", new string[0], null, null)] - [InlineData("{\"url\": \"http://foo.com/chat\"}", null, null, "http://foo.com/chat", null)] - [InlineData("{\"url\": \"http://foo.com/chat\", \"accessToken\": \"token\"}", null, null, "http://foo.com/chat", "token")] - [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[{\"transport\":\"test\",\"transferFormats\":[]}]}", "123", new[] { "test" }, null, null)] - [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[{\"\\u0074ransport\":\"test\",\"transferFormats\":[]}]}", "123", new[] { "test" }, null, null)] - public void ParsingNegotiateResponseMessageSuccessForValid(string json, string connectionId, string[] availableTransports, string url, string accessToken) + [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[]}", "123", new string[0], null, null, 0)] + [InlineData("{\"connectionId\":\"\",\"availableTransports\":[]}", "", new string[0], null, null, 0)] + [InlineData("{\"url\": \"http://foo.com/chat\"}", null, null, "http://foo.com/chat", null, 0)] + [InlineData("{\"url\": \"http://foo.com/chat\", \"accessToken\": \"token\"}", null, null, "http://foo.com/chat", "token", 0)] + [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[{\"transport\":\"test\",\"transferFormats\":[]}]}", "123", new[] { "test" }, null, null, 0)] + [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[{\"\\u0074ransport\":\"test\",\"transferFormats\":[]}]}", "123", new[] { "test" }, null, null, 0)] + [InlineData("{\"negotiateVersion\":123,\"connectionId\":\"123\",\"availableTransports\":[{\"\\u0074ransport\":\"test\",\"transferFormats\":[]}]}", "123", new[] { "test" }, null, null, 123)] + [InlineData("{\"negotiateVersion\":123,\"negotiateVersion\":321,\"connectionId\":\"123\",\"availableTransports\":[]}", "123", new string[0], null, null, 321)] + [InlineData("{\"ignore\":123,\"negotiateVersion\":123,\"connectionId\":\"123\",\"availableTransports\":[]}", "123", new string[0], null, null, 123)] + [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[],\"negotiateVersion\":123}", "123", new string[0], null, null, 123)] + public void ParsingNegotiateResponseMessageSuccessForValid(string json, string connectionId, string[] availableTransports, string url, string accessToken, int version) { var responseData = Encoding.UTF8.GetBytes(json); var response = NegotiateProtocol.ParseResponse(responseData); @@ -28,6 +32,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests Assert.Equal(availableTransports?.Length, response.AvailableTransports?.Count); Assert.Equal(url, response.Url); Assert.Equal(accessToken, response.AccessToken); + Assert.Equal(version, response.Version); if (response.AvailableTransports != null) { @@ -83,7 +88,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests string json = Encoding.UTF8.GetString(writer.ToArray()); - Assert.Equal("{\"availableTransports\":[]}", json); + Assert.Equal("{\"negotiateVersion\":0,\"availableTransports\":[]}", json); } } @@ -102,7 +107,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests string json = Encoding.UTF8.GetString(writer.ToArray()); - Assert.Equal("{\"availableTransports\":[{\"transport\":null,\"transferFormats\":[]}]}", json); + Assert.Equal("{\"negotiateVersion\":0,\"availableTransports\":[{\"transport\":null,\"transferFormats\":[]}]}", json); } } } From 3dee6782c3f8d611b3cda871b97778a34121df83 Mon Sep 17 00:00:00 2001 From: John Luo Date: Wed, 4 Sep 2019 14:36:26 -0700 Subject: [PATCH 09/31] Ignore whitespace differences during code check (#13660) --- eng/scripts/CodeCheck.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/scripts/CodeCheck.ps1 b/eng/scripts/CodeCheck.ps1 index 8f1115be47..a072589565 100644 --- a/eng/scripts/CodeCheck.ps1 +++ b/eng/scripts/CodeCheck.ps1 @@ -169,7 +169,7 @@ try { Write-Host "Run git diff to check for pending changes" # Redirect stderr to stdout because PowerShell does not consistently handle output to stderr - $changedFiles = & cmd /c 'git --no-pager diff --ignore-space-at-eol --name-only 2>nul' + $changedFiles = & cmd /c 'git --no-pager diff --ignore-space-change --name-only 2>nul' # Temporary: Disable check for blazor js file $changedFilesExclusion = "src/Components/Web.JS/dist/Release/blazor.server.js" @@ -179,7 +179,7 @@ try { if ($file -eq $changedFilesExclusion) {continue} $filePath = Resolve-Path "${repoRoot}/${file}" LogError "Generated code is not up to date in $file. You might need to regenerate the reference assemblies or project list (see docs/ReferenceAssemblies.md and docs/ReferenceResolution.md)" -filepath $filePath - & git --no-pager diff --ignore-space-at-eol $filePath + & git --no-pager diff --ignore-space-change $filePath } } } From ac9a6f331e3976e6ea170bec5a6964d080400c16 Mon Sep 17 00:00:00 2001 From: Brennan Date: Wed, 4 Sep 2019 19:27:14 -0700 Subject: [PATCH 10/31] Fix User-Agent typo Java (#13692) --- .../src/main/java/com/microsoft/signalr/UserAgentHelper.java | 2 +- .../src/test/java/com/microsoft/signalr/HubConnectionTest.java | 2 +- .../src/test/java/com/microsoft/signalr/UserAgentTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/UserAgentHelper.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/UserAgentHelper.java index 988da567f4..e54809c700 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/UserAgentHelper.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/UserAgentHelper.java @@ -17,7 +17,7 @@ public class UserAgentHelper { // Parsing version numbers String detailedVersion = Version.getDetailedVersion(); agentBuilder.append(getVersion(detailedVersion)); - agentBuilder.append("; ("); + agentBuilder.append(" ("); agentBuilder.append(detailedVersion); agentBuilder.append("; "); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java index 051b5fed83..af01f5311c 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java @@ -1116,7 +1116,7 @@ class HubConnectionTest { hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); AtomicBoolean done = new AtomicBoolean(); - Single result = hubConnection.invoke(String.class, "fixedMessage", null); + Single result = hubConnection.invoke(String.class, "fixedMessage", (Object)null); result.doOnSuccess(value -> done.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"fixedMessage\",\"arguments\":[null]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); assertFalse(done.get()); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/UserAgentTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/UserAgentTest.java index 59715223b9..362b035a27 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/UserAgentTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/UserAgentTest.java @@ -46,7 +46,7 @@ public class UserAgentTest { String detailedVersion = Version.getDetailedVersion(); String handMadeUserAgent = "Microsoft SignalR/" + UserAgentHelper.getVersion(detailedVersion) + - "; (" + detailedVersion + "; " + UserAgentHelper.getOS() + "; Java; " + + " (" + detailedVersion + "; " + UserAgentHelper.getOS() + "; Java; " + UserAgentHelper.getJavaVersion() + "; " + UserAgentHelper.getJavaVendor() + ")"; assertEquals(handMadeUserAgent, userAgent); From 71a8092cb9ca270672b441b19d263c339c62379f Mon Sep 17 00:00:00 2001 From: Harley Adams Date: Thu, 25 Jul 2019 11:20:59 -0700 Subject: [PATCH 11/31] Add FetchHttpClient --- .../ts/signalr/src/DefaultHttpClient.ts | 5 +- .../clients/ts/signalr/src/FetchHttpClient.ts | 130 ++++++++++++++++++ 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts diff --git a/src/SignalR/clients/ts/signalr/src/DefaultHttpClient.ts b/src/SignalR/clients/ts/signalr/src/DefaultHttpClient.ts index fece43020d..8058e5716a 100644 --- a/src/SignalR/clients/ts/signalr/src/DefaultHttpClient.ts +++ b/src/SignalR/clients/ts/signalr/src/DefaultHttpClient.ts @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. import { AbortError } from "./Errors"; +import { FetchHttpClient } from "./FetchHttpClient"; import { HttpClient, HttpRequest, HttpResponse } from "./HttpClient"; import { ILogger } from "./ILogger"; import { NodeHttpClient } from "./NodeHttpClient"; @@ -15,7 +16,9 @@ export class DefaultHttpClient extends HttpClient { public constructor(logger: ILogger) { super(); - if (typeof XMLHttpRequest !== "undefined") { + if (typeof fetch !== "undefined") { + this.httpClient = new FetchHttpClient(logger); + } else if (typeof XMLHttpRequest !== "undefined") { this.httpClient = new XhrHttpClient(logger); } else { this.httpClient = new NodeHttpClient(logger); diff --git a/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts b/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts new file mode 100644 index 0000000000..0b4c2754a7 --- /dev/null +++ b/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts @@ -0,0 +1,130 @@ +// 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. + +import { AbortError, HttpError, TimeoutError } from "./Errors"; +import { HttpClient, HttpRequest, HttpResponse } from "./HttpClient"; +import { ILogger, LogLevel } from "./ILogger"; + +export class FetchHttpClient extends HttpClient { + private readonly logger: ILogger; + + public constructor(logger: ILogger) { + super(); + this.logger = logger; + } + + /** @inheritDoc */ + public send(request: HttpRequest): Promise { + // Check that abort was not signaled before calling send + if (request.abortSignal && request.abortSignal.aborted) { + return Promise.reject(new AbortError()); + } + + if (!request.method) { + return Promise.reject(new Error("No method defined.")); + } + if (!request.url) { + return Promise.reject(new Error("No url defined.")); + } + + return new Promise((resolve, reject) => { + const abortController = new AbortController(); + + const fetchRequest = new Request(request.url!, { + body: request.content!, + cache: "no-cache", + credentials: "include", + headers: { + "Content-Type": "text/plain;charset=UTF-8", + "X-Requested-With": "Fetch", + ...request.headers, + }, + method: request.method!, + mode: "cors", + signal: abortController.signal, + }); + + // Hook our abourtSignal into the abort controller + if (request.abortSignal) { + request.abortSignal.onabort = () => { + abortController.abort(); + reject(new AbortError()); + }; + } + + // If a timeout has been passed in setup a timeout to call abort + // Type needs to be any to fit window.setTimeout and NodeJS.setTimeout + let timeoutId: any = null; + if (request.timeout) { + const msTimeout = request.timeout!; + timeoutId = setTimeout(() => { + abortController.abort(); + reject(new TimeoutError()); + }, msTimeout); + } + + fetch(fetchRequest) + .then((response: Response) => { + if (timeoutId) { + clearTimeout(timeoutId); + } + if (!response.ok) { + throw new Error(`${response.status}: ${response.statusText}.`); + } else { + return response; + } + }) + .then((response: Response) => { + if (request.abortSignal) { + request.abortSignal.onabort = null; + } + + const content = deserializeContent(response, request.responseType); + + content.then((payload) => { + resolve(new HttpResponse( + response.status, + response.statusText, + payload, + )); + }).catch(() => { + reject(new HttpError(response.statusText, response.status)); + }); + }) + .catch((error) => { + this.logger.log( + LogLevel.Warning, + `Error from HTTP request. ${error.message}.`, + ); + const [statusText, status] = error.message.split(":"); + reject(new HttpError(statusText, status)); + }); + }); + } +} + +function deserializeContent(response: Response, responseType?: XMLHttpRequestResponseType): Promise { + let content; + switch (responseType) { + case "arraybuffer": + content = response.arrayBuffer(); + break; + case "blob": + content = response.blob(); + break; + case "document": + content = response.json(); + break; + case "json": + content = response.json(); + break; + case "text": + content = response.text(); + break; + default: + content = response.text(); + break; + } + + return content; +} From 2be0ffae194c320472235173b8a18ab6c6ef78d0 Mon Sep 17 00:00:00 2001 From: Harley Adams Date: Mon, 29 Jul 2019 11:07:42 -0700 Subject: [PATCH 12/31] Add eachHttpClient test option to get basic coverage for new fetchhttpclient --- .../clients/ts/FunctionalTests/ts/Common.ts | 11 +- .../ts/FunctionalTests/ts/ConnectionTests.ts | 205 +++++++++--------- src/SignalR/clients/ts/signalr/src/index.ts | 2 + 3 files changed, 117 insertions(+), 101 deletions(-) diff --git a/src/SignalR/clients/ts/FunctionalTests/ts/Common.ts b/src/SignalR/clients/ts/FunctionalTests/ts/Common.ts index 2bb33c1c11..15c03ef6d0 100644 --- a/src/SignalR/clients/ts/FunctionalTests/ts/Common.ts +++ b/src/SignalR/clients/ts/FunctionalTests/ts/Common.ts @@ -1,8 +1,9 @@ // 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. -import { HttpTransportType, IHubProtocol, JsonHubProtocol } from "@microsoft/signalr"; +import { DefaultHttpClient, FetchHttpClient, HttpClient, HttpTransportType, IHubProtocol, JsonHubProtocol, XhrHttpClient } from "@microsoft/signalr"; import { MessagePackHubProtocol } from "@microsoft/signalr-protocol-msgpack"; +import { TestLogger } from "./TestLogger"; // On slower CI machines, these tests sometimes take longer than 5s jasmine.DEFAULT_TIMEOUT_INTERVAL = 20 * 1000; @@ -100,3 +101,11 @@ export function eachTransportAndProtocol(action: (transport: HttpTransportType, export function getGlobalObject(): any { return typeof window !== "undefined" ? window : global; } + +export function eachHttpClient(action: (transport: HttpClient) => void) { + const httpClients: HttpClient[] = [new FetchHttpClient(TestLogger.instance), new XhrHttpClient(TestLogger.instance), new DefaultHttpClient(TestLogger.instance)]; + + return httpClients.forEach((t) => { + return action(t); + }); +} diff --git a/src/SignalR/clients/ts/FunctionalTests/ts/ConnectionTests.ts b/src/SignalR/clients/ts/FunctionalTests/ts/ConnectionTests.ts index 7760294123..9d60a38ff6 100644 --- a/src/SignalR/clients/ts/FunctionalTests/ts/ConnectionTests.ts +++ b/src/SignalR/clients/ts/FunctionalTests/ts/ConnectionTests.ts @@ -5,7 +5,7 @@ // tslint:disable:no-floating-promises import { HttpTransportType, IHttpConnectionOptions, TransferFormat } from "@microsoft/signalr"; -import { eachTransport, ECHOENDPOINT_URL } from "./Common"; +import { eachHttpClient, eachTransport, ECHOENDPOINT_URL } from "./Common"; import { TestLogger } from "./TestLogger"; // We want to continue testing HttpConnection, but we don't export it anymore. So just pull it in directly from the source file. @@ -44,109 +44,114 @@ describe("connection", () => { }); eachTransport((transportType) => { - describe(`over ${HttpTransportType[transportType]}`, () => { - it("can send and receive messages", (done) => { - const message = "Hello World!"; - // the url should be resolved relative to the document.location.host - // and the leading '/' should be automatically added to the url - const connection = new HttpConnection(ECHOENDPOINT_URL, { - ...commonOptions, - transport: transportType, - }); + eachHttpClient((httpClient) => { + describe(`over ${HttpTransportType[transportType]}`, () => { + it("can send and receive messages", (done) => { + const message = "Hello World!"; + // the url should be resolved relative to the document.location.host + // and the leading '/' should be automatically added to the url + const connection = new HttpConnection(ECHOENDPOINT_URL, { + ...commonOptions, + httpClient, + transport: transportType, + }); - connection.onreceive = (data: any) => { - if (data === message) { - connection.stop(); - } - }; - - connection.onclose = (error: any) => { - expect(error).toBeUndefined(); - done(); - }; - - connection.start(TransferFormat.Text).then(() => { - connection.send(message); - }).catch((e: any) => { - fail(e); - done(); - }); - }); - - it("does not log content of messages sent or received by default", (done) => { - TestLogger.saveLogsAndReset(); - const message = "Hello World!"; - - // DON'T use commonOptions because we want to specifically test the scenario where logMessageContent is not set. - const connection = new HttpConnection(ECHOENDPOINT_URL, { - logger: TestLogger.instance, - transport: transportType, - }); - - connection.onreceive = (data: any) => { - if (data === message) { - connection.stop(); - } - }; - - // @ts-ignore: We don't use the error parameter intentionally. - connection.onclose = (error) => { - // Search the logs for the message content - expect(TestLogger.instance.currentLog.messages.length).toBeGreaterThan(0); - // @ts-ignore: We don't use the _ or __ parameters intentionally. - for (const [_, __, logMessage] of TestLogger.instance.currentLog.messages) { - expect(logMessage).not.toContain(message); - } - done(); - }; - - connection.start(TransferFormat.Text).then(() => { - connection.send(message); - }).catch((e) => { - fail(e); - done(); - }); - }); - - it("does log content of messages sent or received when enabled", (done) => { - TestLogger.saveLogsAndReset(); - const message = "Hello World!"; - - // DON'T use commonOptions because we want to specifically test the scenario where logMessageContent is set to true (even if commonOptions changes). - const connection = new HttpConnection(ECHOENDPOINT_URL, { - logMessageContent: true, - logger: TestLogger.instance, - transport: transportType, - }); - - connection.onreceive = (data: any) => { - if (data === message) { - connection.stop(); - } - }; - - // @ts-ignore: We don't use the error parameter intentionally. - connection.onclose = (error) => { - // Search the logs for the message content - let matches = 0; - expect(TestLogger.instance.currentLog.messages.length).toBeGreaterThan(0); - // @ts-ignore: We don't use the _ or __ parameters intentionally. - for (const [_, __, logMessage] of TestLogger.instance.currentLog.messages) { - if (logMessage.indexOf(message) !== -1) { - matches += 1; + connection.onreceive = (data: any) => { + if (data === message) { + connection.stop(); } - } + }; - // One match for send, one for receive. - expect(matches).toEqual(2); - done(); - }; + connection.onclose = (error: any) => { + expect(error).toBeUndefined(); + done(); + }; - connection.start(TransferFormat.Text).then(() => { - connection.send(message); - }).catch((e: any) => { - fail(e); - done(); + connection.start(TransferFormat.Text).then(() => { + connection.send(message); + }).catch((e: any) => { + fail(e); + done(); + }); + }); + + it("does not log content of messages sent or received by default", (done) => { + TestLogger.saveLogsAndReset(); + const message = "Hello World!"; + + // DON'T use commonOptions because we want to specifically test the scenario where logMessageContent is not set. + const connection = new HttpConnection(ECHOENDPOINT_URL, { + httpClient, + logger: TestLogger.instance, + transport: transportType, + }); + + connection.onreceive = (data: any) => { + if (data === message) { + connection.stop(); + } + }; + + // @ts-ignore: We don't use the error parameter intentionally. + connection.onclose = (error) => { + // Search the logs for the message content + expect(TestLogger.instance.currentLog.messages.length).toBeGreaterThan(0); + // @ts-ignore: We don't use the _ or __ parameters intentionally. + for (const [_, __, logMessage] of TestLogger.instance.currentLog.messages) { + expect(logMessage).not.toContain(message); + } + done(); + }; + + connection.start(TransferFormat.Text).then(() => { + connection.send(message); + }).catch((e) => { + fail(e); + done(); + }); + }); + + it("does log content of messages sent or received when enabled", (done) => { + TestLogger.saveLogsAndReset(); + const message = "Hello World!"; + + // DON'T use commonOptions because we want to specifically test the scenario where logMessageContent is set to true (even if commonOptions changes). + const connection = new HttpConnection(ECHOENDPOINT_URL, { + httpClient, + logMessageContent: true, + logger: TestLogger.instance, + transport: transportType, + }); + + connection.onreceive = (data: any) => { + if (data === message) { + connection.stop(); + } + }; + + // @ts-ignore: We don't use the error parameter intentionally. + connection.onclose = (error) => { + // Search the logs for the message content + let matches = 0; + expect(TestLogger.instance.currentLog.messages.length).toBeGreaterThan(0); + // @ts-ignore: We don't use the _ or __ parameters intentionally. + for (const [_, __, logMessage] of TestLogger.instance.currentLog.messages) { + if (logMessage.indexOf(message) !== -1) { + matches += 1; + } + } + + // One match for send, one for receive. + expect(matches).toEqual(2); + done(); + }; + + connection.start(TransferFormat.Text).then(() => { + connection.send(message); + }).catch((e: any) => { + fail(e); + done(); + }); }); }); }); diff --git a/src/SignalR/clients/ts/signalr/src/index.ts b/src/SignalR/clients/ts/signalr/src/index.ts index dc2086c661..8d93b7530f 100644 --- a/src/SignalR/clients/ts/signalr/src/index.ts +++ b/src/SignalR/clients/ts/signalr/src/index.ts @@ -10,6 +10,7 @@ export { AbortSignal } from "./AbortController"; export { AbortError, HttpError, TimeoutError } from "./Errors"; export { HttpClient, HttpRequest, HttpResponse } from "./HttpClient"; export { DefaultHttpClient } from "./DefaultHttpClient"; +export { FetchHttpClient } from "./FetchHttpClient"; export { IHttpConnectionOptions } from "./IHttpConnectionOptions"; export { HubConnection, HubConnectionState } from "./HubConnection"; export { HubConnectionBuilder } from "./HubConnectionBuilder"; @@ -22,3 +23,4 @@ export { NullLogger } from "./Loggers"; export { JsonHubProtocol } from "./JsonHubProtocol"; export { Subject } from "./Subject"; export { IRetryPolicy, RetryContext } from "./IRetryPolicy"; +export { XhrHttpClient } from "./XhrHttpClient"; From 7317bb16a9cd5a834c0468a74113055031262217 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Wed, 28 Aug 2019 11:13:53 -0700 Subject: [PATCH 13/31] Cleanup FetchHttpClient --- .../clients/ts/FunctionalTests/ts/Common.ts | 33 ++++- .../ts/FunctionalTests/ts/ConnectionTests.ts | 2 +- .../FunctionalTests/ts/HubConnectionTests.ts | 42 +++--- .../clients/ts/signalr/src/FetchHttpClient.ts | 131 ++++++++---------- .../clients/ts/signalr/src/HttpClient.ts | 8 ++ src/SignalR/clients/ts/signalr/src/index.ts | 2 - 6 files changed, 120 insertions(+), 98 deletions(-) diff --git a/src/SignalR/clients/ts/FunctionalTests/ts/Common.ts b/src/SignalR/clients/ts/FunctionalTests/ts/Common.ts index 15c03ef6d0..c66bff8e49 100644 --- a/src/SignalR/clients/ts/FunctionalTests/ts/Common.ts +++ b/src/SignalR/clients/ts/FunctionalTests/ts/Common.ts @@ -1,10 +1,15 @@ // 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. -import { DefaultHttpClient, FetchHttpClient, HttpClient, HttpTransportType, IHubProtocol, JsonHubProtocol, XhrHttpClient } from "@microsoft/signalr"; +import { HttpClient, HttpTransportType, IHubProtocol, JsonHubProtocol } from "@microsoft/signalr"; import { MessagePackHubProtocol } from "@microsoft/signalr-protocol-msgpack"; import { TestLogger } from "./TestLogger"; +import { FetchHttpClient } from "@microsoft/signalr/dist/esm/FetchHttpClient"; +import { NodeHttpClient } from "@microsoft/signalr/dist/esm/NodeHttpClient"; +import { Platform } from "@microsoft/signalr/dist/esm/Utils"; +import { XhrHttpClient } from "@microsoft/signalr/dist/esm/XhrHttpClient"; + // On slower CI machines, these tests sometimes take longer than 5s jasmine.DEFAULT_TIMEOUT_INTERVAL = 20 * 1000; @@ -98,14 +103,34 @@ export function eachTransportAndProtocol(action: (transport: HttpTransportType, }); } +export function eachTransportAndProtocolAndHttpClient(action: (transport: HttpTransportType, protocol: IHubProtocol, httpClient: HttpClient) => void) { + eachTransportAndProtocol((transport, protocol) => { + getHttpClients().forEach((httpClient) => { + action(transport, protocol, httpClient); + }); + }); +} + export function getGlobalObject(): any { return typeof window !== "undefined" ? window : global; } -export function eachHttpClient(action: (transport: HttpClient) => void) { - const httpClients: HttpClient[] = [new FetchHttpClient(TestLogger.instance), new XhrHttpClient(TestLogger.instance), new DefaultHttpClient(TestLogger.instance)]; +export function getHttpClients(): HttpClient[] { + const httpClients: HttpClient[] = []; + if (typeof XMLHttpRequest !== "undefined") { + httpClients.push(new XhrHttpClient(TestLogger.instance)); + } + if (typeof fetch !== "undefined") { + httpClients.push(new FetchHttpClient(TestLogger.instance)); + } + if (Platform.isNode) { + httpClients.push(new NodeHttpClient(TestLogger.instance)); + } + return httpClients; +} - return httpClients.forEach((t) => { +export function eachHttpClient(action: (transport: HttpClient) => void) { + return getHttpClients().forEach((t) => { return action(t); }); } diff --git a/src/SignalR/clients/ts/FunctionalTests/ts/ConnectionTests.ts b/src/SignalR/clients/ts/FunctionalTests/ts/ConnectionTests.ts index 9d60a38ff6..3b559af265 100644 --- a/src/SignalR/clients/ts/FunctionalTests/ts/ConnectionTests.ts +++ b/src/SignalR/clients/ts/FunctionalTests/ts/ConnectionTests.ts @@ -45,7 +45,7 @@ describe("connection", () => { eachTransport((transportType) => { eachHttpClient((httpClient) => { - describe(`over ${HttpTransportType[transportType]}`, () => { + describe(`over ${HttpTransportType[transportType]} with ${(httpClient.constructor as any).name}`, () => { it("can send and receive messages", (done) => { const message = "Hello World!"; // the url should be resolved relative to the document.location.host diff --git a/src/SignalR/clients/ts/FunctionalTests/ts/HubConnectionTests.ts b/src/SignalR/clients/ts/FunctionalTests/ts/HubConnectionTests.ts index 3d5f434a17..dbfa1a886f 100644 --- a/src/SignalR/clients/ts/FunctionalTests/ts/HubConnectionTests.ts +++ b/src/SignalR/clients/ts/FunctionalTests/ts/HubConnectionTests.ts @@ -7,7 +7,7 @@ import { AbortError, DefaultHttpClient, HttpClient, HttpRequest, HttpResponse, HttpTransportType, HubConnectionBuilder, IHttpConnectionOptions, JsonHubProtocol, NullLogger } from "@microsoft/signalr"; import { MessagePackHubProtocol } from "@microsoft/signalr-protocol-msgpack"; -import { eachTransport, eachTransportAndProtocol, ENDPOINT_BASE_HTTPS_URL, ENDPOINT_BASE_URL } from "./Common"; +import { eachTransport, eachTransportAndProtocolAndHttpClient, ENDPOINT_BASE_HTTPS_URL, ENDPOINT_BASE_URL } from "./Common"; import "./LogBannerReporter"; import { TestLogger } from "./TestLogger"; @@ -49,12 +49,12 @@ function getConnectionBuilder(transportType?: HttpTransportType, url?: string, o } describe("hubConnection", () => { - eachTransportAndProtocol((transportType, protocol) => { + eachTransportAndProtocolAndHttpClient((transportType, protocol, httpClient) => { describe("using " + protocol.name + " over " + HttpTransportType[transportType] + " transport", () => { it("can invoke server method and receive result", (done) => { const message = "你好,世界!"; - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -81,7 +81,7 @@ describe("hubConnection", () => { it("using https, can invoke server method and receive result", (done) => { const message = "你好,世界!"; - const hubConnection = getConnectionBuilder(transportType, TESTHUBENDPOINT_HTTPS_URL) + const hubConnection = getConnectionBuilder(transportType, TESTHUBENDPOINT_HTTPS_URL, { httpClient }) .withHubProtocol(protocol) .build(); @@ -108,7 +108,7 @@ describe("hubConnection", () => { it("can invoke server method non-blocking and not receive result", (done) => { const message = "你好,世界!"; - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -130,7 +130,7 @@ describe("hubConnection", () => { }); it("can invoke server method structural object and receive structural result", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -154,7 +154,7 @@ describe("hubConnection", () => { }); it("can stream server method and receive result", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -185,7 +185,7 @@ describe("hubConnection", () => { }); it("can stream server method and cancel stream", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -219,7 +219,7 @@ describe("hubConnection", () => { it("rethrows an exception from the server when invoking", (done) => { const errorMessage = "An unexpected error occurred invoking 'ThrowException' on the server. InvalidOperationException: An error occurred."; - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -241,7 +241,7 @@ describe("hubConnection", () => { }); it("throws an exception when invoking streaming method with invoke", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -263,7 +263,7 @@ describe("hubConnection", () => { }); it("throws an exception when receiving a streaming result for method called with invoke", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -286,7 +286,7 @@ describe("hubConnection", () => { it("rethrows an exception from the server when streaming", (done) => { const errorMessage = "An unexpected error occurred invoking 'StreamThrowException' on the server. InvalidOperationException: An error occurred."; - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -313,7 +313,7 @@ describe("hubConnection", () => { }); it("throws an exception when invoking hub method with stream", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -340,7 +340,7 @@ describe("hubConnection", () => { }); it("can receive server calls", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -370,7 +370,7 @@ describe("hubConnection", () => { }); it("can receive server calls without rebinding handler when restarted", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -425,7 +425,7 @@ describe("hubConnection", () => { }); it("closed with error or start fails if hub cannot be created", async (done) => { - const hubConnection = getConnectionBuilder(transportType, ENDPOINT_BASE_URL + "/uncreatable") + const hubConnection = getConnectionBuilder(transportType, ENDPOINT_BASE_URL + "/uncreatable", { httpClient }) .withHubProtocol(protocol) .build(); @@ -446,7 +446,7 @@ describe("hubConnection", () => { }); it("can handle different types", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -489,7 +489,7 @@ describe("hubConnection", () => { }); it("can receive different types", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -534,7 +534,7 @@ describe("hubConnection", () => { it("can be restarted", (done) => { const message = "你好,世界!"; - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -577,7 +577,7 @@ describe("hubConnection", () => { }); it("can stream from client to server with rxjs", async (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -594,7 +594,7 @@ describe("hubConnection", () => { }); it("can stream from client to server and close with error with rxjs", async (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); diff --git a/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts b/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts index 0b4c2754a7..48840b42c2 100644 --- a/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts +++ b/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts @@ -14,113 +14,104 @@ export class FetchHttpClient extends HttpClient { } /** @inheritDoc */ - public send(request: HttpRequest): Promise { + public async send(request: HttpRequest): Promise { // Check that abort was not signaled before calling send if (request.abortSignal && request.abortSignal.aborted) { - return Promise.reject(new AbortError()); + throw new AbortError(); } if (!request.method) { - return Promise.reject(new Error("No method defined.")); + throw new Error("No method defined."); } if (!request.url) { - return Promise.reject(new Error("No url defined.")); + throw new Error("No url defined."); } - return new Promise((resolve, reject) => { - const abortController = new AbortController(); + const abortController = new AbortController(); - const fetchRequest = new Request(request.url!, { + let error: any; + // Hook our abortSignal into the abort controller + if (request.abortSignal) { + request.abortSignal.onabort = () => { + abortController.abort(); + error = new AbortError(); + }; + } + + // If a timeout has been passed in, setup a timeout to call abort + // Type needs to be any to fit window.setTimeout and NodeJS.setTimeout + let timeoutId: any = null; + if (request.timeout) { + const msTimeout = request.timeout!; + timeoutId = setTimeout(() => { + abortController.abort(); + this.logger.log(LogLevel.Warning, `Timeout from HTTP request.`); + error = new TimeoutError(); + }, msTimeout); + } + + let response: Response; + try { + response = await fetch(request.url!, { body: request.content!, cache: "no-cache", credentials: "include", headers: { "Content-Type": "text/plain;charset=UTF-8", - "X-Requested-With": "Fetch", + "X-Requested-With": "XMLHttpRequest", ...request.headers, }, method: request.method!, mode: "cors", + redirect: "manual", signal: abortController.signal, }); - - // Hook our abourtSignal into the abort controller + } catch (e) { + if (error) { + throw error; + } + this.logger.log( + LogLevel.Warning, + `Error from HTTP request. ${e}.`, + ); + throw e; + } finally { + if (timeoutId) { + clearTimeout(timeoutId); + } if (request.abortSignal) { - request.abortSignal.onabort = () => { - abortController.abort(); - reject(new AbortError()); - }; + request.abortSignal.onabort = null; } + } - // If a timeout has been passed in setup a timeout to call abort - // Type needs to be any to fit window.setTimeout and NodeJS.setTimeout - let timeoutId: any = null; - if (request.timeout) { - const msTimeout = request.timeout!; - timeoutId = setTimeout(() => { - abortController.abort(); - reject(new TimeoutError()); - }, msTimeout); - } + if (!response.ok) { + throw new HttpError(response.statusText, response.status); + } - fetch(fetchRequest) - .then((response: Response) => { - if (timeoutId) { - clearTimeout(timeoutId); - } - if (!response.ok) { - throw new Error(`${response.status}: ${response.statusText}.`); - } else { - return response; - } - }) - .then((response: Response) => { - if (request.abortSignal) { - request.abortSignal.onabort = null; - } + const content = deserializeContent(response, request.responseType); + const payload = await content; - const content = deserializeContent(response, request.responseType); - - content.then((payload) => { - resolve(new HttpResponse( - response.status, - response.statusText, - payload, - )); - }).catch(() => { - reject(new HttpError(response.statusText, response.status)); - }); - }) - .catch((error) => { - this.logger.log( - LogLevel.Warning, - `Error from HTTP request. ${error.message}.`, - ); - const [statusText, status] = error.message.split(":"); - reject(new HttpError(statusText, status)); - }); - }); + return new HttpResponse( + response.status, + response.statusText, + payload, + ); } } -function deserializeContent(response: Response, responseType?: XMLHttpRequestResponseType): Promise { +function deserializeContent(response: Response, responseType?: XMLHttpRequestResponseType): Promise { let content; switch (responseType) { case "arraybuffer": content = response.arrayBuffer(); break; - case "blob": - content = response.blob(); - break; - case "document": - content = response.json(); - break; - case "json": - content = response.json(); - break; case "text": content = response.text(); break; + case "blob": + case "document": + case "json": + throw new Error(`${responseType} is not supported.`); default: content = response.text(); break; diff --git a/src/SignalR/clients/ts/signalr/src/HttpClient.ts b/src/SignalR/clients/ts/signalr/src/HttpClient.ts index 9685feca5e..c50289bbf1 100644 --- a/src/SignalR/clients/ts/signalr/src/HttpClient.ts +++ b/src/SignalR/clients/ts/signalr/src/HttpClient.ts @@ -57,6 +57,14 @@ export class HttpResponse { * @param {ArrayBuffer} content The content of the response. */ constructor(statusCode: number, statusText: string, content: ArrayBuffer); + + /** Constructs a new instance of {@link @microsoft/signalr.HttpResponse} with the specified status code, message and binary content. + * + * @param {number} statusCode The status code of the response. + * @param {string} statusText The status message of the response. + * @param {string | ArrayBuffer} content The content of the response. + */ + constructor(statusCode: number, statusText: string, content: string | ArrayBuffer); constructor( public readonly statusCode: number, public readonly statusText?: string, diff --git a/src/SignalR/clients/ts/signalr/src/index.ts b/src/SignalR/clients/ts/signalr/src/index.ts index 8d93b7530f..dc2086c661 100644 --- a/src/SignalR/clients/ts/signalr/src/index.ts +++ b/src/SignalR/clients/ts/signalr/src/index.ts @@ -10,7 +10,6 @@ export { AbortSignal } from "./AbortController"; export { AbortError, HttpError, TimeoutError } from "./Errors"; export { HttpClient, HttpRequest, HttpResponse } from "./HttpClient"; export { DefaultHttpClient } from "./DefaultHttpClient"; -export { FetchHttpClient } from "./FetchHttpClient"; export { IHttpConnectionOptions } from "./IHttpConnectionOptions"; export { HubConnection, HubConnectionState } from "./HubConnection"; export { HubConnectionBuilder } from "./HubConnectionBuilder"; @@ -23,4 +22,3 @@ export { NullLogger } from "./Loggers"; export { JsonHubProtocol } from "./JsonHubProtocol"; export { Subject } from "./Subject"; export { IRetryPolicy, RetryContext } from "./IRetryPolicy"; -export { XhrHttpClient } from "./XhrHttpClient"; From a438e221bfd5201b51c978e8aa225a3219a228a4 Mon Sep 17 00:00:00 2001 From: Matt Galbraith Date: Thu, 5 Sep 2019 16:52:23 -0700 Subject: [PATCH 14/31] Add -Force to Expand-Archive As noted in the console spew of this test: https://helix.dot.net/api/2019-06-17/jobs/82bbddae-4a56-4002-b3ee-a8768311a92b/workitems/Microsoft.AspNetCore.SpaServices.Extensions.Tests-netcoreapp3.0/console it's quite possible for Node to be unpacked, but not on the path (generally on a helix machine it's because your parent process was created before the previous guy who unzipped Node and put it on the path ran, so only after reboot would you see the effect you want. --- eng/helix/content/InstallNode.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eng/helix/content/InstallNode.ps1 b/eng/helix/content/InstallNode.ps1 index 862e612582..84425f271b 100644 --- a/eng/helix/content/InstallNode.ps1 +++ b/eng/helix/content/InstallNode.ps1 @@ -48,9 +48,10 @@ Write-Host "Extracting to $tempDir" if (Get-Command -Name 'Microsoft.PowerShell.Archive\Expand-Archive' -ErrorAction Ignore) { # Use built-in commands where possible as they are cross-plat compatible - Microsoft.PowerShell.Archive\Expand-Archive -Path "nodejs.zip" -DestinationPath $tempDir + Microsoft.PowerShell.Archive\Expand-Archive -Path "nodejs.zip" -DestinationPath $tempDir -Force } else { + Remove-Item $tempDir -Recurse -ErrorAction Ignore # Fallback to old approach for old installations of PowerShell Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory("nodejs.zip", $tempDir) From 1ea43c549234d46814c446fda1680bf6f96922a4 Mon Sep 17 00:00:00 2001 From: Alessandro Ghidini Date: Mon, 9 Sep 2019 10:34:20 +0200 Subject: [PATCH 15/31] Ensure blazor client side logger checks for enabled log levels (#12928) --- .../Blazor/Blazor/src/Services/WebAssemblyConsoleLogger.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Components/Blazor/Blazor/src/Services/WebAssemblyConsoleLogger.cs b/src/Components/Blazor/Blazor/src/Services/WebAssemblyConsoleLogger.cs index c86c1cf30b..1769dbd915 100644 --- a/src/Components/Blazor/Blazor/src/Services/WebAssemblyConsoleLogger.cs +++ b/src/Components/Blazor/Blazor/src/Services/WebAssemblyConsoleLogger.cs @@ -20,6 +20,11 @@ namespace Microsoft.AspNetCore.Blazor.Services public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { + if (!IsEnabled(logLevel)) + { + return; + } + var formattedMessage = formatter(state, exception); Console.WriteLine($"[{logLevel}] {formattedMessage}"); } From 64157c95c1f10992e81e8590e31e90b0b8bd5eeb Mon Sep 17 00:00:00 2001 From: jeuxjeux20 Date: Mon, 9 Sep 2019 23:22:10 +0200 Subject: [PATCH 16/31] Typo fix on Problem's documentation (#13812) --- src/Mvc/Mvc.Core/src/ControllerBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mvc/Mvc.Core/src/ControllerBase.cs b/src/Mvc/Mvc.Core/src/ControllerBase.cs index e502ca144a..3590d6146f 100644 --- a/src/Mvc/Mvc.Core/src/ControllerBase.cs +++ b/src/Mvc/Mvc.Core/src/ControllerBase.cs @@ -1848,7 +1848,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// Creates an that produces a response. /// - /// The value for .. + /// The value for . /// The value for . /// The value for . /// The value for . From be33a6f392b6182f253a930823003082ab5843c1 Mon Sep 17 00:00:00 2001 From: Kirk Larkin <6025110+serpent5@users.noreply.github.com> Date: Wed, 11 Sep 2019 16:09:20 +0100 Subject: [PATCH 17/31] Fixed suggestion re using AddCookie(s) for authz (#13869) --- src/Http/Authentication.Core/src/AuthenticationService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Http/Authentication.Core/src/AuthenticationService.cs b/src/Http/Authentication.Core/src/AuthenticationService.cs index 7efce69ce8..9cc0807539 100644 --- a/src/Http/Authentication.Core/src/AuthenticationService.cs +++ b/src/Http/Authentication.Core/src/AuthenticationService.cs @@ -253,7 +253,7 @@ namespace Microsoft.AspNetCore.Authentication var schemes = await GetAllSignInSchemeNames(); // CookieAuth is the only implementation of sign-in. - var footer = $" Did you forget to call AddAuthentication().AddCookies(\"{scheme}\",...)?"; + var footer = $" Did you forget to call AddAuthentication().AddCookie(\"{scheme}\",...)?"; if (string.IsNullOrEmpty(schemes)) { @@ -275,7 +275,7 @@ namespace Microsoft.AspNetCore.Authentication { // CookieAuth is the only implementation of sign-in. return new InvalidOperationException(mismatchError - + $"Did you forget to call AddAuthentication().AddCookies(\"Cookies\") and SignInAsync(\"Cookies\",...)?"); + + $"Did you forget to call AddAuthentication().AddCookie(\"Cookies\") and SignInAsync(\"Cookies\",...)?"); } return new InvalidOperationException(mismatchError + $"The registered sign-in schemes are: {schemes}."); @@ -292,7 +292,7 @@ namespace Microsoft.AspNetCore.Authentication { var schemes = await GetAllSignOutSchemeNames(); - var footer = $" Did you forget to call AddAuthentication().AddCookies(\"{scheme}\",...)?"; + var footer = $" Did you forget to call AddAuthentication().AddCookie(\"{scheme}\",...)?"; if (string.IsNullOrEmpty(schemes)) { @@ -314,7 +314,7 @@ namespace Microsoft.AspNetCore.Authentication { // CookieAuth is the most common implementation of sign-out, but OpenIdConnect and WsFederation also support it. return new InvalidOperationException(mismatchError - + $"Did you forget to call AddAuthentication().AddCookies(\"Cookies\") and {nameof(SignOutAsync)}(\"Cookies\",...)?"); + + $"Did you forget to call AddAuthentication().AddCookie(\"Cookies\") and {nameof(SignOutAsync)}(\"Cookies\",...)?"); } return new InvalidOperationException(mismatchError + $"The registered sign-out schemes are: {schemes}."); From 9557630c0a470618b30679e8effcf019b1c74164 Mon Sep 17 00:00:00 2001 From: Brennan Date: Wed, 11 Sep 2019 09:11:19 -0700 Subject: [PATCH 18/31] [SignalR] Remove a few minor allocations (#13872) --- .../csharp/Client.Core/src/HubConnection.cs | 2 +- .../Core/src/DefaultHubLifetimeManager.cs | 22 +++++++++---------- .../Core/src/Internal/DefaultHubDispatcher.cs | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs b/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs index 61a3aceb55..576e69326f 100644 --- a/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs +++ b/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs @@ -539,7 +539,7 @@ namespace Microsoft.AspNetCore.SignalR.Client /// public IAsyncEnumerable StreamAsyncCore(string methodName, object[] args, CancellationToken cancellationToken = default) { - var cts = cancellationToken.CanBeCanceled ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken) : new CancellationTokenSource(); + var cts = cancellationToken.CanBeCanceled ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, default) : new CancellationTokenSource(); var stream = CastIAsyncEnumerable(methodName, args, cts); var cancelableStream = AsyncEnumerableAdapters.MakeCancelableTypedAsyncEnumerable(stream, cts); return cancelableStream; diff --git a/src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs b/src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs index 3c835ab933..a8a9fe2095 100644 --- a/src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs +++ b/src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs @@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.SignalR return SendToAllConnections(methodName, args, null); } - private Task SendToAllConnections(string methodName, object[] args, Func include) + private Task SendToAllConnections(string methodName, object[] args, Func include, object state = null) { List tasks = null; SerializedHubMessage message = null; @@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.SignalR // foreach over HubConnectionStore avoids allocating an enumerator foreach (var connection in _connections) { - if (include != null && !include(connection)) + if (include != null && !include(connection, state)) { continue; } @@ -127,12 +127,12 @@ namespace Microsoft.AspNetCore.SignalR // Tasks and message are passed by ref so they can be lazily created inside the method post-filtering, // while still being re-usable when sending to multiple groups - private void SendToGroupConnections(string methodName, object[] args, ConcurrentDictionary connections, Func include, ref List tasks, ref SerializedHubMessage message) + private void SendToGroupConnections(string methodName, object[] args, ConcurrentDictionary connections, Func include, object state, ref List tasks, ref SerializedHubMessage message) { // foreach over ConcurrentDictionary avoids allocating an enumerator foreach (var connection in connections) { - if (include != null && !include(connection.Value)) + if (include != null && !include(connection.Value, state)) { continue; } @@ -193,7 +193,7 @@ namespace Microsoft.AspNetCore.SignalR // group might be modified inbetween checking and sending List tasks = null; SerializedHubMessage message = null; - SendToGroupConnections(methodName, args, group, null, ref tasks, ref message); + SendToGroupConnections(methodName, args, group, null, null, ref tasks, ref message); if (tasks != null) { @@ -221,7 +221,7 @@ namespace Microsoft.AspNetCore.SignalR var group = _groups[groupName]; if (group != null) { - SendToGroupConnections(methodName, args, group, null, ref tasks, ref message); + SendToGroupConnections(methodName, args, group, null, null, ref tasks, ref message); } } @@ -247,7 +247,7 @@ namespace Microsoft.AspNetCore.SignalR List tasks = null; SerializedHubMessage message = null; - SendToGroupConnections(methodName, args, group, connection => !excludedConnectionIds.Contains(connection.ConnectionId), ref tasks, ref message); + SendToGroupConnections(methodName, args, group, (connection, state) => !((IReadOnlyList)state).Contains(connection.ConnectionId), excludedConnectionIds, ref tasks, ref message); if (tasks != null) { @@ -271,7 +271,7 @@ namespace Microsoft.AspNetCore.SignalR /// public override Task SendUserAsync(string userId, string methodName, object[] args, CancellationToken cancellationToken = default) { - return SendToAllConnections(methodName, args, connection => string.Equals(connection.UserIdentifier, userId, StringComparison.Ordinal)); + return SendToAllConnections(methodName, args, (connection, state) => string.Equals(connection.UserIdentifier, (string)state, StringComparison.Ordinal), userId); } /// @@ -292,19 +292,19 @@ namespace Microsoft.AspNetCore.SignalR /// public override Task SendAllExceptAsync(string methodName, object[] args, IReadOnlyList excludedConnectionIds, CancellationToken cancellationToken = default) { - return SendToAllConnections(methodName, args, connection => !excludedConnectionIds.Contains(connection.ConnectionId)); + return SendToAllConnections(methodName, args, (connection, state) => !((IReadOnlyList)state).Contains(connection.ConnectionId), excludedConnectionIds); } /// public override Task SendConnectionsAsync(IReadOnlyList connectionIds, string methodName, object[] args, CancellationToken cancellationToken = default) { - return SendToAllConnections(methodName, args, connection => connectionIds.Contains(connection.ConnectionId)); + return SendToAllConnections(methodName, args, (connection, state) => ((IReadOnlyList)state).Contains(connection.ConnectionId), connectionIds); } /// public override Task SendUsersAsync(IReadOnlyList userIds, string methodName, object[] args, CancellationToken cancellationToken = default) { - return SendToAllConnections(methodName, args, connection => userIds.Contains(connection.UserIdentifier)); + return SendToAllConnections(methodName, args, (connection, state) => ((IReadOnlyList)state).Contains(connection.UserIdentifier), userIds); } } } diff --git a/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs b/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs index cead347976..29ae26319c 100644 --- a/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs +++ b/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs @@ -275,7 +275,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal { if (descriptor.OriginalParameterTypes[parameterPointer] == typeof(CancellationToken)) { - cts = CancellationTokenSource.CreateLinkedTokenSource(connection.ConnectionAborted); + cts = CancellationTokenSource.CreateLinkedTokenSource(connection.ConnectionAborted, default); arguments[parameterPointer] = cts.Token; } else if (isStreamCall && ReflectionHelper.IsStreamingType(descriptor.OriginalParameterTypes[parameterPointer], mustBeDirectType: true)) @@ -308,7 +308,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal return; } - cts = cts ?? CancellationTokenSource.CreateLinkedTokenSource(connection.ConnectionAborted); + cts = cts ?? CancellationTokenSource.CreateLinkedTokenSource(connection.ConnectionAborted, default); connection.ActiveRequestCancellationSources.TryAdd(hubMethodInvocationMessage.InvocationId, cts); var enumerable = descriptor.FromReturnedStream(result, cts.Token); From 71c5c66b211c7ab9d44751e39cef27c525124bb7 Mon Sep 17 00:00:00 2001 From: Devin Garner Date: Wed, 11 Sep 2019 12:16:57 -0600 Subject: [PATCH 19/31] Fixes #6306. Make StreamReader send last line's contents to listener, otherwise AngularCliBuilder doesn't know when build:ssr is complete (#13485) Thanks for your effort. --- .../SpaServices.Extensions/src/Util/EventedStreamReader.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Middleware/SpaServices.Extensions/src/Util/EventedStreamReader.cs b/src/Middleware/SpaServices.Extensions/src/Util/EventedStreamReader.cs index 298d5289d6..aafd630853 100644 --- a/src/Middleware/SpaServices.Extensions/src/Util/EventedStreamReader.cs +++ b/src/Middleware/SpaServices.Extensions/src/Util/EventedStreamReader.cs @@ -83,6 +83,12 @@ namespace Microsoft.AspNetCore.NodeServices.Util var chunkLength = await _streamReader.ReadAsync(buf, 0, buf.Length); if (chunkLength == 0) { + if (_linesBuffer.Length > 0) + { + OnCompleteLine(_linesBuffer.ToString()); + _linesBuffer.Clear(); + } + OnClosed(); break; } From 96f1c92caa484e6b9c7e5d3768c7dbe70287d87f Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 12 Sep 2019 07:43:53 +0900 Subject: [PATCH 20/31] Add support for port 0 in HttpSys (#13841) --- .../src/Common/TestPortHelper.cs | 29 ------- .../src/Common/TestUriHelper.cs | 7 +- src/Servers/HttpSys/src/MessagePump.cs | 36 +++++---- .../HttpSys/src/NativeInterop/UrlGroup.cs | 3 +- .../HttpSys/src/UrlPrefixCollection.cs | 66 +++++++++++++-- src/Servers/HttpSys/startvs.cmd | 3 + .../test/FunctionalTests/MessagePumpTests.cs | 5 +- .../HttpSys/test/FunctionalTests/Utilities.cs | 81 +++++++------------ .../HttpSys/test/Tests/UrlPrefixTests.cs | 2 +- .../NativeInterop/UnsafeNativeMethods.cs | 2 + 10 files changed, 125 insertions(+), 109 deletions(-) create mode 100644 src/Servers/HttpSys/startvs.cmd diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/TestPortHelper.cs b/src/Hosting/Server.IntegrationTesting/src/Common/TestPortHelper.cs index 7129ff73d3..b8688dec04 100644 --- a/src/Hosting/Server.IntegrationTesting/src/Common/TestPortHelper.cs +++ b/src/Hosting/Server.IntegrationTesting/src/Common/TestPortHelper.cs @@ -56,34 +56,5 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common } } } - - private const int BasePort = 5001; - private const int MaxPort = 8000; - private static int NextPort = BasePort; - - // GetNextPort doesn't check for HttpSys urlacls. - public static int GetNextHttpSysPort(string scheme) - { - while (NextPort < MaxPort) - { - var port = NextPort++; - - using (var server = new HttpListener()) - { - server.Prefixes.Add($"{scheme}://localhost:{port}/"); - try - { - server.Start(); - server.Stop(); - return port; - } - catch (HttpListenerException) - { - } - } - } - NextPort = BasePort; - throw new Exception("Failed to locate a free port."); - } } } diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/TestUriHelper.cs b/src/Hosting/Server.IntegrationTesting/src/Common/TestUriHelper.cs index aaac5b88a7..4899fbea4f 100644 --- a/src/Hosting/Server.IntegrationTesting/src/Common/TestUriHelper.cs +++ b/src/Hosting/Server.IntegrationTesting/src/Common/TestUriHelper.cs @@ -1,7 +1,9 @@ -// 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; +using System.Diagnostics; + namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common { public static class TestUriHelper @@ -34,7 +36,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common } else if (serverType == ServerType.HttpSys) { - return new UriBuilder(scheme, "localhost", TestPortHelper.GetNextHttpSysPort(scheme)).Uri; + Debug.Assert(scheme == "http", "Https not supported"); + return new UriBuilder(scheme, "localhost", 0).Uri; } else { diff --git a/src/Servers/HttpSys/src/MessagePump.cs b/src/Servers/HttpSys/src/MessagePump.cs index f829d6e92d..40efc20b79 100644 --- a/src/Servers/HttpSys/src/MessagePump.cs +++ b/src/Servers/HttpSys/src/MessagePump.cs @@ -2,16 +2,18 @@ // 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.Diagnostics.Contracts; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.HttpSys.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Microsoft.AspNetCore.HttpSys.Internal; namespace Microsoft.AspNetCore.Server.HttpSys { @@ -74,6 +76,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys } var hostingUrlsPresent = _serverAddresses.Addresses.Count > 0; + var serverAddressCopy = _serverAddresses.Addresses.ToList(); + _serverAddresses.Addresses.Clear(); if (_serverAddresses.PreferHostingUrls && hostingUrlsPresent) { @@ -85,10 +89,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys Listener.Options.UrlPrefixes.Clear(); } - foreach (var value in _serverAddresses.Addresses) - { - Listener.Options.UrlPrefixes.Add(value); - } + UpdateUrlPrefixes(serverAddressCopy); } else if (_options.UrlPrefixes.Count > 0) { @@ -100,23 +101,15 @@ namespace Microsoft.AspNetCore.Server.HttpSys _serverAddresses.Addresses.Clear(); } - foreach (var prefix in _options.UrlPrefixes) - { - _serverAddresses.Addresses.Add(prefix.FullPrefix); - } } else if (hostingUrlsPresent) { - foreach (var value in _serverAddresses.Addresses) - { - Listener.Options.UrlPrefixes.Add(value); - } + UpdateUrlPrefixes(serverAddressCopy); } else { LogHelper.LogDebug(_logger, $"No listening endpoints were configured. Binding to {Constants.DefaultServerAddress} by default."); - _serverAddresses.Addresses.Add(Constants.DefaultServerAddress); Listener.Options.UrlPrefixes.Add(Constants.DefaultServerAddress); } @@ -129,6 +122,13 @@ namespace Microsoft.AspNetCore.Server.HttpSys Listener.Start(); + // Update server addresses after we start listening as port 0 + // needs to be selected at the point of binding. + foreach (var prefix in _options.UrlPrefixes) + { + _serverAddresses.Addresses.Add(prefix.FullPrefix); + } + ActivateRequestProcessingLimits(); return Task.CompletedTask; @@ -142,6 +142,14 @@ namespace Microsoft.AspNetCore.Server.HttpSys } } + private void UpdateUrlPrefixes(IList serverAddressCopy) + { + foreach (var value in serverAddressCopy) + { + Listener.Options.UrlPrefixes.Add(value); + } + } + // The message pump. // When we start listening for the next request on one thread, we may need to be sure that the // completion continues on another thread as to not block the current request processing. diff --git a/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs b/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs index 8f33c7b678..87a5012641 100644 --- a/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs +++ b/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs @@ -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; @@ -73,7 +73,6 @@ namespace Microsoft.AspNetCore.Server.HttpSys { LogHelper.LogInfo(_logger, "Listening on prefix: " + uriPrefix); CheckDisposed(); - var statusCode = HttpApi.HttpAddUrlToUrlGroup(Id, uriPrefix, (ulong)contextId, 0); if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) diff --git a/src/Servers/HttpSys/src/UrlPrefixCollection.cs b/src/Servers/HttpSys/src/UrlPrefixCollection.cs index 92c50aea09..954b3d6d6f 100644 --- a/src/Servers/HttpSys/src/UrlPrefixCollection.cs +++ b/src/Servers/HttpSys/src/UrlPrefixCollection.cs @@ -1,8 +1,13 @@ -// 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; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.AspNetCore.HttpSys.Internal; namespace Microsoft.AspNetCore.Server.HttpSys { @@ -15,6 +20,12 @@ namespace Microsoft.AspNetCore.Server.HttpSys private UrlGroup _urlGroup; private int _nextId = 1; + // Valid port range of 5000 - 48000. + private const int BasePort = 5000; + private const int MaxPortIndex = 43000; + private const int MaxRetries = 1000; + private static int NextPortIndex; + internal UrlPrefixCollection() { } @@ -138,10 +149,55 @@ namespace Microsoft.AspNetCore.Server.HttpSys { _urlGroup = urlGroup; // go through the uri list and register for each one of them - foreach (var pair in _prefixes) + // Call ToList to avoid modification when enumerating. + foreach (var pair in _prefixes.ToList()) { - // We'll get this index back on each request and use it to look up the prefix to calculate PathBase. - _urlGroup.RegisterPrefix(pair.Value.FullPrefix, pair.Key); + var urlPrefix = pair.Value; + if (urlPrefix.PortValue == 0) + { + if (urlPrefix.IsHttps) + { + throw new InvalidOperationException("Cannot bind to port 0 with https."); + } + + FindHttpPortUnsynchronized(pair.Key, urlPrefix); + } + else + { + // We'll get this index back on each request and use it to look up the prefix to calculate PathBase. + _urlGroup.RegisterPrefix(pair.Value.FullPrefix, pair.Key); + } + } + } + } + + private void FindHttpPortUnsynchronized(int key, UrlPrefix urlPrefix) + { + for (var index = 0; index < MaxRetries; index++) + { + try + { + // Bit of complicated math to always try 3000 ports, starting from NextPortIndex + 5000, + // circling back around if we go above 8000 back to 5000, and so on. + var port = ((index + NextPortIndex) % MaxPortIndex) + BasePort; + + Debug.Assert(port >= 5000 || port < 8000); + + var newPrefix = UrlPrefix.Create(urlPrefix.Scheme, urlPrefix.Host, port, urlPrefix.Path); + _urlGroup.RegisterPrefix(newPrefix.FullPrefix, key); + _prefixes[key] = newPrefix; + + NextPortIndex += index + 1; + return; + } + catch (HttpSysException ex) + { + if ((ex.ErrorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_ACCESS_DENIED + && ex.ErrorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SHARING_VIOLATION + && ex.ErrorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_ALREADY_EXISTS) || index == MaxRetries - 1) + { + throw; + } } } } @@ -159,4 +215,4 @@ namespace Microsoft.AspNetCore.Server.HttpSys } } } -} \ No newline at end of file +} diff --git a/src/Servers/HttpSys/startvs.cmd b/src/Servers/HttpSys/startvs.cmd new file mode 100644 index 0000000000..94b00042d2 --- /dev/null +++ b/src/Servers/HttpSys/startvs.cmd @@ -0,0 +1,3 @@ +@ECHO OFF + +%~dp0..\..\..\startvs.cmd %~dp0HttpSysServer.sln diff --git a/src/Servers/HttpSys/test/FunctionalTests/MessagePumpTests.cs b/src/Servers/HttpSys/test/FunctionalTests/MessagePumpTests.cs index 5fc93e69e7..faddf29f71 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/MessagePumpTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/MessagePumpTests.cs @@ -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.Linq; @@ -114,7 +114,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys { server.StartAsync(new DummyApplication(), CancellationToken.None).Wait(); - Assert.Equal(Constants.DefaultServerAddress, server.Features.Get().Addresses.Single()); + // Trailing slash is added when put in UrlPrefix. + Assert.StartsWith(Constants.DefaultServerAddress, server.Features.Get().Addresses.Single()); } } diff --git a/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs b/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs index 24869a8b80..63fa8e5760 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; @@ -21,11 +22,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys { // When tests projects are run in parallel, overlapping port ranges can cause a race condition when looking for free // ports during dynamic port allocation. - private const int BasePort = 5001; - private const int MaxPort = 8000; private const int BaseHttpsPort = 44300; private const int MaxHttpsPort = 44399; - private static int NextPort = BasePort; private static int NextHttpsPort = BaseHttpsPort; private static object PortLock = new object(); internal static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(15); @@ -84,39 +82,26 @@ namespace Microsoft.AspNetCore.Server.HttpSys internal static IWebHost CreateDynamicHost(string basePath, out string root, out string baseAddress, Action configureOptions, RequestDelegate app) { - lock (PortLock) - { - while (NextPort < MaxPort) + var prefix = UrlPrefix.Create("http", "localhost", "0", basePath); + + var builder = new WebHostBuilder() + .UseHttpSys(options => { - var port = NextPort++; - var prefix = UrlPrefix.Create("http", "localhost", port, basePath); - root = prefix.Scheme + "://" + prefix.Host + ":" + prefix.Port; - baseAddress = prefix.ToString(); + options.UrlPrefixes.Add(prefix); + configureOptions(options); + }) + .Configure(appBuilder => appBuilder.Run(app)); - var builder = new WebHostBuilder() - .UseHttpSys(options => - { - options.UrlPrefixes.Add(prefix); - configureOptions(options); - }) - .Configure(appBuilder => appBuilder.Run(app)); + var host = builder.Build(); - var host = builder.Build(); + host.Start(); + var options = host.Services.GetRequiredService>(); + prefix = options.Value.UrlPrefixes.First(); // Has new port + root = prefix.Scheme + "://" + prefix.Host + ":" + prefix.Port; + baseAddress = prefix.ToString(); - try - { - host.Start(); - return host; - } - catch (HttpSysException) - { - } - - } - NextPort = BasePort; - } - throw new Exception("Failed to locate a free port."); + return host; } internal static MessagePump CreatePump() @@ -124,31 +109,18 @@ namespace Microsoft.AspNetCore.Server.HttpSys internal static IServer CreateDynamicHttpServer(string basePath, out string root, out string baseAddress, Action configureOptions, RequestDelegate app) { - lock (PortLock) - { - while (NextPort < MaxPort) - { + var prefix = UrlPrefix.Create("http", "localhost", "0", basePath); - var port = NextPort++; - var prefix = UrlPrefix.Create("http", "localhost", port, basePath); - root = prefix.Scheme + "://" + prefix.Host + ":" + prefix.Port; - baseAddress = prefix.ToString(); + var server = CreatePump(); + server.Features.Get().Addresses.Add(prefix.ToString()); + configureOptions(server.Listener.Options); + server.StartAsync(new DummyApplication(app), CancellationToken.None).Wait(); - var server = CreatePump(); - server.Features.Get().Addresses.Add(baseAddress); - configureOptions(server.Listener.Options); - try - { - server.StartAsync(new DummyApplication(app), CancellationToken.None).Wait(); - return server; - } - catch (HttpSysException) - { - } - } - NextPort = BasePort; - } - throw new Exception("Failed to locate a free port."); + prefix = server.Listener.Options.UrlPrefixes.First(); // Has new port + root = prefix.Scheme + "://" + prefix.Host + ":" + prefix.Port; + baseAddress = prefix.ToString(); + + return server; } internal static IServer CreateDynamicHttpsServer(out string baseAddress, RequestDelegate app) @@ -184,6 +156,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys throw new Exception("Failed to locate a free port."); } + internal static Task WithTimeout(this Task task) => task.TimeoutAfter(DefaultTimeout); internal static Task WithTimeout(this Task task) => task.TimeoutAfter(DefaultTimeout); diff --git a/src/Servers/HttpSys/test/Tests/UrlPrefixTests.cs b/src/Servers/HttpSys/test/Tests/UrlPrefixTests.cs index 8614ac36db..20d0f0713f 100644 --- a/src/Servers/HttpSys/test/Tests/UrlPrefixTests.cs +++ b/src/Servers/HttpSys/test/Tests/UrlPrefixTests.cs @@ -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; diff --git a/src/Shared/HttpSys/NativeInterop/UnsafeNativeMethods.cs b/src/Shared/HttpSys/NativeInterop/UnsafeNativeMethods.cs index 4483cbc306..4316e020e1 100644 --- a/src/Shared/HttpSys/NativeInterop/UnsafeNativeMethods.cs +++ b/src/Shared/HttpSys/NativeInterop/UnsafeNativeMethods.cs @@ -24,6 +24,8 @@ namespace Microsoft.AspNetCore.HttpSys.Internal internal static class ErrorCodes { internal const uint ERROR_SUCCESS = 0; + internal const uint ERROR_ACCESS_DENIED = 5; + internal const uint ERROR_SHARING_VIOLATION = 32; internal const uint ERROR_HANDLE_EOF = 38; internal const uint ERROR_NOT_SUPPORTED = 50; internal const uint ERROR_INVALID_PARAMETER = 87; From 67555a020b4ca075169f1e0adde58f08d252f5ea Mon Sep 17 00:00:00 2001 From: Vincent Costel Date: Thu, 12 Sep 2019 00:17:21 -0400 Subject: [PATCH 21/31] Use the correct disposed object name (#13871) The name of the disposed object should be FileBufferingWriteStream instead of FileBufferingReadStream --- src/Http/WebUtilities/src/FileBufferingWriteStream.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/WebUtilities/src/FileBufferingWriteStream.cs b/src/Http/WebUtilities/src/FileBufferingWriteStream.cs index fd9c993bad..da24578135 100644 --- a/src/Http/WebUtilities/src/FileBufferingWriteStream.cs +++ b/src/Http/WebUtilities/src/FileBufferingWriteStream.cs @@ -238,7 +238,7 @@ namespace Microsoft.AspNetCore.WebUtilities { if (Disposed) { - throw new ObjectDisposedException(nameof(FileBufferingReadStream)); + throw new ObjectDisposedException(nameof(FileBufferingWriteStream)); } } From bea980286e83319d291088e5962bcc276755275d Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Thu, 12 Sep 2019 00:44:33 -0700 Subject: [PATCH 22/31] Remove MacOS helix queue for now (#13907) --- eng/targets/Helix.Common.props | 1 - 1 file changed, 1 deletion(-) diff --git a/eng/targets/Helix.Common.props b/eng/targets/Helix.Common.props index 8ea26294a2..d35d51c8b0 100644 --- a/eng/targets/Helix.Common.props +++ b/eng/targets/Helix.Common.props @@ -18,7 +18,6 @@ - From 8257369ec1ed8c341f35b3e161a6fd460c3e8ac5 Mon Sep 17 00:00:00 2001 From: Kahbazi Date: Fri, 13 Sep 2019 01:58:46 +0430 Subject: [PATCH 23/31] Add support for cancellation token in typed client hub (#13816) --- .../Core/src/Internal/TypedClientBuilder.cs | 22 ++++++++-- .../test/Internal/TypedClientBuilderTests.cs | 41 +++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs b/src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs index 511280d636..a333c600a8 100644 --- a/src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs +++ b/src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs @@ -132,6 +132,14 @@ namespace Microsoft.AspNetCore.SignalR.Internal methodBuilder.DefineGenericParameters(genericTypeNames); } + // Check to see if the last parameter of the method is a CancellationToken + bool hasCancellationToken = paramTypes.LastOrDefault() == typeof(CancellationToken); + if (hasCancellationToken) + { + // remove CancellationToken from input paramTypes + paramTypes = paramTypes.Take(paramTypes.Length - 1).ToArray(); + } + var generator = methodBuilder.GetILGenerator(); // Declare local variable to store the arguments to IClientProxy.SendCoreAsync @@ -145,7 +153,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal generator.Emit(OpCodes.Ldstr, interfaceMethodInfo.Name); // Create an new object array to hold all the parameters to this method - generator.Emit(OpCodes.Ldc_I4, parameters.Length); // Stack: + generator.Emit(OpCodes.Ldc_I4, paramTypes.Length); // Stack: generator.Emit(OpCodes.Newarr, typeof(object)); // allocate object array generator.Emit(OpCodes.Stloc_0); @@ -162,8 +170,16 @@ namespace Microsoft.AspNetCore.SignalR.Internal // Load parameter array on to the stack. generator.Emit(OpCodes.Ldloc_0); - // Get 'CancellationToken.None' and put it on the stack, since we don't support CancellationToken right now - generator.Emit(OpCodes.Call, CancellationTokenNoneProperty.GetMethod); + if (hasCancellationToken) + { + // Get CancellationToken from input argument and put it on the stack + generator.Emit(OpCodes.Ldarg, paramTypes.Length + 1); + } + else + { + // Get 'CancellationToken.None' and put it on the stack, for when method does not have CancellationToken + generator.Emit(OpCodes.Call, CancellationTokenNoneProperty.GetMethod); + } // Send! generator.Emit(OpCodes.Callvirt, invokeMethod); diff --git a/src/SignalR/server/SignalR/test/Internal/TypedClientBuilderTests.cs b/src/SignalR/server/SignalR/test/Internal/TypedClientBuilderTests.cs index 4f68f6fe74..9f412af8f4 100644 --- a/src/SignalR/server/SignalR/test/Internal/TypedClientBuilderTests.cs +++ b/src/SignalR/server/SignalR/test/Internal/TypedClientBuilderTests.cs @@ -75,6 +75,41 @@ namespace Microsoft.AspNetCore.SignalR.Tests.Internal await task2.OrTimeout(); } + [Fact] + public async Task SupportsCancellationToken() + { + var clientProxy = new MockProxy(); + var typedProxy = TypedClientBuilder.Build(clientProxy); + CancellationTokenSource cts1 = new CancellationTokenSource(); + var task1 = typedProxy.Method("foo", cts1.Token); + Assert.False(task1.IsCompleted); + + CancellationTokenSource cts2 = new CancellationTokenSource(); + var task2 = typedProxy.NoArgumentMethod(cts2.Token); + Assert.False(task2.IsCompleted); + + Assert.Collection(clientProxy.Sends, + send1 => + { + Assert.Equal("Method", send1.Method); + Assert.Equal(1, send1.Arguments.Length); + Assert.Collection(send1.Arguments, + arg1 => Assert.Equal("foo", arg1)); + Assert.Equal(cts1.Token, send1.CancellationToken); + send1.Complete(); + }, + send2 => + { + Assert.Equal("NoArgumentMethod", send2.Method); + Assert.Equal(0, send2.Arguments.Length); + Assert.Equal(cts2.Token, send2.CancellationToken); + send2.Complete(); + }); + + await task1.OrTimeout(); + await task2.OrTimeout(); + } + [Fact] public void ThrowsIfProvidedAClass() { @@ -179,6 +214,12 @@ namespace Microsoft.AspNetCore.SignalR.Tests.Internal Task SubMethod(string foo); } + public interface ICancellationTokenMethod + { + Task Method(string foo, CancellationToken cancellationToken); + Task NoArgumentMethod(CancellationToken cancellationToken); + } + public interface IPropertiesClient { string Property { get; } From 9e788650b091875e4bf15eb7ae7f05283845c5b0 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Thu, 12 Sep 2019 18:11:20 -0700 Subject: [PATCH 24/31] Fix typo in arm64 logs (#13936) --- .azure/pipelines/helix-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure/pipelines/helix-test.yml b/.azure/pipelines/helix-test.yml index ed10e8fc2c..c8cc32edc4 100644 --- a/.azure/pipelines/helix-test.yml +++ b/.azure/pipelines/helix-test.yml @@ -43,5 +43,5 @@ jobs: installNodeJs: false artifacts: - name: Helix_arm64_logs - path: artifacts/logs/ + path: artifacts/log/ publishOnError: true From 7ebb03b6e02986bfede8d5bd97ea6cd6cdcc578f Mon Sep 17 00:00:00 2001 From: John Luo Date: Thu, 12 Sep 2019 22:56:09 -0700 Subject: [PATCH 25/31] Fix TFM issue in templates after netcoreapp5.0 conversion (#13934) --- .../Web.ProjectTemplates/BlazorServerWeb-CSharp.csproj.in | 2 +- .../Web.ProjectTemplates/EmptyWeb-CSharp.csproj.in | 2 +- .../Web.ProjectTemplates/EmptyWeb-FSharp.fsproj.in | 2 +- .../Web.ProjectTemplates/GrpcService-CSharp.csproj.in | 2 +- .../Microsoft.DotNet.Web.ProjectTemplates.csproj | 1 + .../Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in | 2 +- .../Web.ProjectTemplates/StarterWeb-CSharp.csproj.in | 2 +- .../Web.ProjectTemplates/StarterWeb-FSharp.fsproj.in | 2 +- .../Web.ProjectTemplates/WebApi-CSharp.csproj.in | 2 +- .../Web.ProjectTemplates/WebApi-FSharp.fsproj.in | 2 +- .../Web.ProjectTemplates/Worker-CSharp.csproj.in | 2 +- .../Web.Spa.ProjectTemplates/Angular-CSharp.csproj.in | 2 +- .../Microsoft.DotNet.Web.Spa.ProjectTemplates.csproj | 1 + .../Web.Spa.ProjectTemplates/React-CSharp.csproj.in | 2 +- .../Web.Spa.ProjectTemplates/ReactRedux-CSharp.csproj.in | 2 +- 15 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/BlazorServerWeb-CSharp.csproj.in b/src/ProjectTemplates/Web.ProjectTemplates/BlazorServerWeb-CSharp.csproj.in index 34f8880205..b4020a5d98 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/BlazorServerWeb-CSharp.csproj.in +++ b/src/ProjectTemplates/Web.ProjectTemplates/BlazorServerWeb-CSharp.csproj.in @@ -1,7 +1,7 @@ - $(DefaultNetCoreTargetFramework) + ${DefaultNetCoreTargetFramework} aspnet-BlazorServerWeb_CSharp-53bc9b9d-9d6a-45d4-8429-2a2761773502 0 1 diff --git a/src/ProjectTemplates/Web.ProjectTemplates/EmptyWeb-CSharp.csproj.in b/src/ProjectTemplates/Web.ProjectTemplates/EmptyWeb-CSharp.csproj.in index 57d248b353..f685d2d72d 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/EmptyWeb-CSharp.csproj.in +++ b/src/ProjectTemplates/Web.ProjectTemplates/EmptyWeb-CSharp.csproj.in @@ -1,7 +1,7 @@  - $(DefaultNetCoreTargetFramework) + ${DefaultNetCoreTargetFramework} True Company.WebApplication1 diff --git a/src/ProjectTemplates/Web.ProjectTemplates/EmptyWeb-FSharp.fsproj.in b/src/ProjectTemplates/Web.ProjectTemplates/EmptyWeb-FSharp.fsproj.in index 0207f21188..0deda84e88 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/EmptyWeb-FSharp.fsproj.in +++ b/src/ProjectTemplates/Web.ProjectTemplates/EmptyWeb-FSharp.fsproj.in @@ -1,7 +1,7 @@  - $(DefaultNetCoreTargetFramework) + ${DefaultNetCoreTargetFramework} True Company.WebApplication1 diff --git a/src/ProjectTemplates/Web.ProjectTemplates/GrpcService-CSharp.csproj.in b/src/ProjectTemplates/Web.ProjectTemplates/GrpcService-CSharp.csproj.in index 219779fedc..60e843ffc0 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/GrpcService-CSharp.csproj.in +++ b/src/ProjectTemplates/Web.ProjectTemplates/GrpcService-CSharp.csproj.in @@ -1,7 +1,7 @@ - $(DefaultNetCoreTargetFramework) + ${DefaultNetCoreTargetFramework} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/Microsoft.DotNet.Web.ProjectTemplates.csproj b/src/ProjectTemplates/Web.ProjectTemplates/Microsoft.DotNet.Web.ProjectTemplates.csproj index 1edd2c6202..ef85eb3657 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/Microsoft.DotNet.Web.ProjectTemplates.csproj +++ b/src/ProjectTemplates/Web.ProjectTemplates/Microsoft.DotNet.Web.ProjectTemplates.csproj @@ -10,6 +10,7 @@ + DefaultNetCoreTargetFramework=$(DefaultNetCoreTargetFramework); GrpcAspNetCorePackageVersion=$(GrpcAspNetCorePackageVersion); MicrosoftAspNetCoreMvcRazorRuntimeCompilationPackageVersion=$(MicrosoftAspNetCoreMvcRazorRuntimeCompilationPackageVersion); MicrosoftEntityFrameworkCoreSqlitePackageVersion=$(MicrosoftEntityFrameworkCoreSqlitePackageVersion); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in b/src/ProjectTemplates/Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in index 583df61028..133951239b 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in +++ b/src/ProjectTemplates/Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in @@ -1,7 +1,7 @@ - $(DefaultNetCoreTargetFramework) + ${DefaultNetCoreTargetFramework} aspnet-Company.WebApplication1-0ce56475-d1db-490f-8af1-a881ea4fcd2d 0 1 diff --git a/src/ProjectTemplates/Web.ProjectTemplates/StarterWeb-CSharp.csproj.in b/src/ProjectTemplates/Web.ProjectTemplates/StarterWeb-CSharp.csproj.in index ebb5bcea7a..e31adf4f35 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/StarterWeb-CSharp.csproj.in +++ b/src/ProjectTemplates/Web.ProjectTemplates/StarterWeb-CSharp.csproj.in @@ -1,7 +1,7 @@ - $(DefaultNetCoreTargetFramework) + ${DefaultNetCoreTargetFramework} aspnet-Company.WebApplication1-53bc9b9d-9d6a-45d4-8429-2a2761773502 0 1 diff --git a/src/ProjectTemplates/Web.ProjectTemplates/StarterWeb-FSharp.fsproj.in b/src/ProjectTemplates/Web.ProjectTemplates/StarterWeb-FSharp.fsproj.in index a6195f707a..f9390a53f5 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/StarterWeb-FSharp.fsproj.in +++ b/src/ProjectTemplates/Web.ProjectTemplates/StarterWeb-FSharp.fsproj.in @@ -1,7 +1,7 @@  - $(DefaultNetCoreTargetFramework) + ${DefaultNetCoreTargetFramework} True Company.WebApplication1 diff --git a/src/ProjectTemplates/Web.ProjectTemplates/WebApi-CSharp.csproj.in b/src/ProjectTemplates/Web.ProjectTemplates/WebApi-CSharp.csproj.in index 56eba69b2d..b7b2ada343 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/WebApi-CSharp.csproj.in +++ b/src/ProjectTemplates/Web.ProjectTemplates/WebApi-CSharp.csproj.in @@ -1,7 +1,7 @@  - $(DefaultNetCoreTargetFramework) + ${DefaultNetCoreTargetFramework} aspnet-Company.WebApplication1-53bc9b9d-9d6a-45d4-8429-2a2761773502 0 1 diff --git a/src/ProjectTemplates/Web.ProjectTemplates/WebApi-FSharp.fsproj.in b/src/ProjectTemplates/Web.ProjectTemplates/WebApi-FSharp.fsproj.in index 7bb70a4ffc..ec9d1aa17b 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/WebApi-FSharp.fsproj.in +++ b/src/ProjectTemplates/Web.ProjectTemplates/WebApi-FSharp.fsproj.in @@ -1,7 +1,7 @@  - $(DefaultNetCoreTargetFramework) + ${DefaultNetCoreTargetFramework} True Company.WebApplication1 diff --git a/src/ProjectTemplates/Web.ProjectTemplates/Worker-CSharp.csproj.in b/src/ProjectTemplates/Web.ProjectTemplates/Worker-CSharp.csproj.in index ea86dafef3..3e486cf4de 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/Worker-CSharp.csproj.in +++ b/src/ProjectTemplates/Web.ProjectTemplates/Worker-CSharp.csproj.in @@ -1,7 +1,7 @@ - $(DefaultNetCoreTargetFramework) + ${DefaultNetCoreTargetFramework} dotnet-Company.Application1-53bc9b9d-9d6a-45d4-8429-2a2761773502 True Company.Application1 diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/Angular-CSharp.csproj.in b/src/ProjectTemplates/Web.Spa.ProjectTemplates/Angular-CSharp.csproj.in index ab27fa4dbb..d674e304cd 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/Angular-CSharp.csproj.in +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/Angular-CSharp.csproj.in @@ -1,7 +1,7 @@ - $(DefaultNetCoreTargetFramework) + ${DefaultNetCoreTargetFramework} true Latest false diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/Microsoft.DotNet.Web.Spa.ProjectTemplates.csproj b/src/ProjectTemplates/Web.Spa.ProjectTemplates/Microsoft.DotNet.Web.Spa.ProjectTemplates.csproj index f49aad670b..5849cee8bb 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/Microsoft.DotNet.Web.Spa.ProjectTemplates.csproj +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/Microsoft.DotNet.Web.Spa.ProjectTemplates.csproj @@ -11,6 +11,7 @@ + DefaultNetCoreTargetFramework=$(DefaultNetCoreTargetFramework); MicrosoftEntityFrameworkCoreSqlitePackageVersion=$(MicrosoftEntityFrameworkCoreSqlitePackageVersion); MicrosoftEntityFrameworkCoreRelationalPackageVersion=$(MicrosoftEntityFrameworkCoreRelationalPackageVersion); MicrosoftEntityFrameworkCoreSqlServerPackageVersion=$(MicrosoftEntityFrameworkCoreSqlServerPackageVersion); diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/React-CSharp.csproj.in b/src/ProjectTemplates/Web.Spa.ProjectTemplates/React-CSharp.csproj.in index c8d87d8b09..087f141b11 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/React-CSharp.csproj.in +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/React-CSharp.csproj.in @@ -1,7 +1,7 @@ - $(DefaultNetCoreTargetFramework) + ${DefaultNetCoreTargetFramework} true Latest false diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/ReactRedux-CSharp.csproj.in b/src/ProjectTemplates/Web.Spa.ProjectTemplates/ReactRedux-CSharp.csproj.in index a71779d5bd..9ccab92e8b 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/ReactRedux-CSharp.csproj.in +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/ReactRedux-CSharp.csproj.in @@ -1,7 +1,7 @@ - $(DefaultNetCoreTargetFramework) + ${DefaultNetCoreTargetFramework} true Latest false From 7b0b2980dc6ffb860f2bcd71868d98d85c7d35b6 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Fri, 13 Sep 2019 09:34:41 -0700 Subject: [PATCH 26/31] [Helix] Move helix arm run to ci (#13939) --- .azure/pipelines/ci.yml | 20 ++++++++++++++++++++ .azure/pipelines/helix-test.yml | 20 -------------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml index d9dadaaee6..ddedd45cf1 100644 --- a/.azure/pipelines/ci.yml +++ b/.azure/pipelines/ci.yml @@ -487,6 +487,26 @@ jobs: path: artifacts/TestResults/ publishOnError: true +# Helix ARM64 +- template: jobs/default-build.yml + parameters: + jobName: Helix_arm64 + jobDisplayName: "Tests: Helix ARM64" + agentOs: Linux + timeoutInMinutes: 240 + steps: + - script: ./restore.sh -ci + displayName: Restore + - script: ./build.sh -ci --arch arm64 -test --no-build-nodejs -projects $(Build.SourcesDirectory)/eng/helix/helix.proj /p:IsHelixJob=true /p:BuildAllProjects=true /p:BuildNative=true -bl + displayName: Run build.sh helix arm64 target + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) # We need to set this env var to publish helix results to Azure Dev Ops + installNodeJs: false + artifacts: + - name: Helix_arm64_logs + path: artifacts/log/ + publishOnError: true + # Source build - job: Source_Build displayName: 'Test: Linux Source Build' diff --git a/.azure/pipelines/helix-test.yml b/.azure/pipelines/helix-test.yml index c8cc32edc4..ad17b9963e 100644 --- a/.azure/pipelines/helix-test.yml +++ b/.azure/pipelines/helix-test.yml @@ -25,23 +25,3 @@ jobs: - name: Helix_logs path: artifacts/log/ publishOnError: true - -# Build Helix ARM64 -- template: jobs/default-build.yml - parameters: - jobName: Helix_arm64 - jobDisplayName: "Tests: Helix ARM64" - agentOs: Linux - timeoutInMinutes: 240 - steps: - - script: ./restore.sh -ci - displayName: Restore - - script: ./build.sh -ci --arch arm64 -test --no-build-nodejs -projects $(Build.SourcesDirectory)/eng/helix/helix.proj /p:IsHelixJob=true /p:BuildAllProjects=true /p:BuildNative=true -bl - displayName: Run build.sh helix arm64 target - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) # We need to set this env var to publish helix results to Azure Dev Ops - installNodeJs: false - artifacts: - - name: Helix_arm64_logs - path: artifacts/log/ - publishOnError: true From afdf1443a930941711c316dd7713f4e7d3b1ca2b Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 16 Sep 2019 08:28:22 -0700 Subject: [PATCH 27/31] Correct code comment (#13993) --- src/Mvc/Mvc.Abstractions/src/ModelBinding/IValueProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/IValueProvider.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/IValueProvider.cs index 4e57509298..55d1f63af9 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/IValueProvider.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/IValueProvider.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// Retrieves a value object using the specified key. /// /// The key of the value object to retrieve. - /// The value object for the specified key. If the exact key is not found, null. + /// The value object for the specified key. If the exact key is not found, . ValueProviderResult GetValue(string key); } } From dee93d9c34cc8246ea4eb92fe9b994601e7551f9 Mon Sep 17 00:00:00 2001 From: Anthony Chu Date: Mon, 16 Sep 2019 11:48:48 -0400 Subject: [PATCH 28/31] Mention SignalR Service explicitly in NPM README (#12924) --- src/SignalR/clients/ts/signalr/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/SignalR/clients/ts/signalr/README.md b/src/SignalR/clients/ts/signalr/README.md index 0f1c6f705a..3bf0d6534f 100644 --- a/src/SignalR/clients/ts/signalr/README.md +++ b/src/SignalR/clients/ts/signalr/README.md @@ -1,4 +1,4 @@ -JavaScript and TypeScript clients for SignalR for ASP.NET Core +JavaScript and TypeScript clients for SignalR for ASP.NET Core and Azure SignalR Service ## Installation @@ -14,6 +14,8 @@ yarn add @microsoft/signalr See the [SignalR Documentation](https://docs.microsoft.com/aspnet/core/signalr) at docs.microsoft.com for documentation on the latest release. [API Reference Documentation](https://docs.microsoft.com/javascript/api/%40aspnet/signalr/?view=signalr-js-latest) is also available on docs.microsoft.com. +For documentation on using this client with Azure SignalR Service and Azure Functions, see the [SignalR Service serverless developer guide](https://docs.microsoft.com/azure/azure-signalr/signalr-concept-serverless-development-config). + ### Browser To use the client in a browser, copy `*.js` files from the `dist/browser` folder to your script folder include on your page using the `