From 78d5c384714721a587538c57864ac7c11d7a4095 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 21 Mar 2018 23:02:55 -0700 Subject: [PATCH 1/8] Copy HttpContext properties for long polling transport - The long polling transport simulates a persistent connection over multiple http requests. In order to expose common http request properties, we need to copy them to a fake http context on the first poll and set that as the HttpContext exposed via the IHttpContextFeature. --- .../HttpConnectionContextExtensions.cs | 2 +- .../HttpConnectionDispatcher.cs | 86 ++++++++++++- .../Microsoft.AspNetCore.Sockets.Http.csproj | 1 + .../HttpConnectionDispatcherTests.cs | 116 ++++++++++++++++++ 4 files changed, 198 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionContextExtensions.cs b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionContextExtensions.cs index 0118376e34..67a4d968d6 100644 --- a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionContextExtensions.cs +++ b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionContextExtensions.cs @@ -11,7 +11,7 @@ public static class DefaultConnectionContextExtensions { public static HttpContext GetHttpContext(this ConnectionContext connection) { - return connection.Features.Get().HttpContext; + return connection.Features.Get()?.HttpContext; } public static void SetHttpContext(this ConnectionContext connection, HttpContext httpContext) diff --git a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs index 25f239877a..0a4ce9be74 100644 --- a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs +++ b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs @@ -2,14 +2,17 @@ // 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; using System.IO; using System.IO.Pipelines; +using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Protocols.Features; using Microsoft.AspNetCore.Sockets.Internal; @@ -276,8 +279,6 @@ private async Task ExecuteEndpointAsync(HttpContext context, ConnectionDelegate connection.Status = DefaultConnectionContext.ConnectionStatus.Inactive; - connection.SetHttpContext(null); - // Dispose the cancellation token connection.Cancellation.Dispose(); @@ -488,15 +489,28 @@ private async Task EnsureConnectionStateAsync(DefaultConnectionContext con return false; } + // Setup the connection state from the http context + connection.User = context.User; + // Configure transport-specific features. if (transportType == TransportType.LongPolling) { connection.Features.Set(new ConnectionInherentKeepAliveFeature(options.LongPolling.PollTimeout)); - } - // Setup the connection state from the http context - connection.User = context.User; - connection.SetHttpContext(context); + // For long polling, the requests come and go but the connection is still alive. + // to make the IHttpContextFeature work well, we make a copy of the relevant properties + // to a new HttpContext. This means that it's impossible to affect the context + // with subsequent requests. + if (connection.GetHttpContext() == null) + { + var httpContext = CloneHttpContext(context); + connection.SetHttpContext(httpContext); + } + } + else + { + connection.SetHttpContext(context); + } // Set the Connection ID on the logging scope so that logs from now on will have the // Connection ID metadata set. @@ -505,6 +519,60 @@ private async Task EnsureConnectionStateAsync(DefaultConnectionContext con return true; } + private static HttpContext CloneHttpContext(HttpContext context) + { + // The reason we're copying the base features instead of the HttpContext properties is + // so that we can get all of the logic built into DefaultHttpContext to extract higher level + // structure from the low level properties + var existingRequestFeature = context.Features.Get(); + + var requestFeature = new HttpRequestFeature(); + requestFeature.Protocol = existingRequestFeature.Protocol; + requestFeature.Method = existingRequestFeature.Method; + requestFeature.Scheme = existingRequestFeature.Scheme; + requestFeature.Path = existingRequestFeature.Path; + requestFeature.PathBase = existingRequestFeature.PathBase; + requestFeature.QueryString = existingRequestFeature.QueryString; + requestFeature.RawTarget = existingRequestFeature.RawTarget; + var requestHeaders = new Dictionary(existingRequestFeature.Headers.Count); + foreach (var header in existingRequestFeature.Headers) + { + requestHeaders[header.Key] = header.Value; + } + requestFeature.Headers = new HeaderDictionary(requestHeaders); + + var existingConnectionFeature = context.Features.Get(); + var connectionFeature = new HttpConnectionFeature(); + connectionFeature.ConnectionId = existingConnectionFeature.ConnectionId; + connectionFeature.LocalIpAddress = existingConnectionFeature.LocalIpAddress; + connectionFeature.LocalPort = existingConnectionFeature.LocalPort; + connectionFeature.RemoteIpAddress = existingConnectionFeature.RemoteIpAddress; + connectionFeature.RemotePort = existingConnectionFeature.RemotePort; + + // The response is a dud, you can't do anything with it anyways + var responseFeature = new HttpResponseFeature(); + + var features = new FeatureCollection(); + features.Set(requestFeature); + features.Set(responseFeature); + features.Set(connectionFeature); + + // REVIEW: We could strategically look at adding other features but it might be better + // if the expose a callback that would allow the user to preserve HttpContext properties. + + var newHttpContext = new DefaultHttpContext(features); + newHttpContext.TraceIdentifier = context.TraceIdentifier; + newHttpContext.User = context.User; + + // Making request services function property could be tricky and expensive as it would require + // DI scope per connection. + newHttpContext.RequestServices = EmptyServiceProvider.Instance; + + // REVIEW: This extends the lifetime of anything that got put into HttpContext.Items + newHttpContext.Items = context.Items.ToDictionary(p => p.Key, p => p.Value); + return newHttpContext; + } + private async Task GetConnectionAsync(HttpContext context, HttpSocketOptions options) { var connectionId = GetConnectionId(context); @@ -568,5 +636,11 @@ private async Task GetOrCreateConnectionAsync(HttpCont return connection; } + + private class EmptyServiceProvider : IServiceProvider + { + public static EmptyServiceProvider Instance { get; } = new EmptyServiceProvider(); + public object GetService(Type serviceType) => null; + } } } diff --git a/src/Microsoft.AspNetCore.Sockets.Http/Microsoft.AspNetCore.Sockets.Http.csproj b/src/Microsoft.AspNetCore.Sockets.Http/Microsoft.AspNetCore.Sockets.Http.csproj index 6a97d4da6e..6c12890f74 100644 --- a/src/Microsoft.AspNetCore.Sockets.Http/Microsoft.AspNetCore.Sockets.Http.csproj +++ b/src/Microsoft.AspNetCore.Sockets.Http/Microsoft.AspNetCore.Sockets.Http.csproj @@ -19,6 +19,7 @@ + diff --git a/test/Microsoft.AspNetCore.Sockets.Tests/HttpConnectionDispatcherTests.cs b/test/Microsoft.AspNetCore.Sockets.Tests/HttpConnectionDispatcherTests.cs index b129825ba4..54815a6ae2 100644 --- a/test/Microsoft.AspNetCore.Sockets.Tests/HttpConnectionDispatcherTests.cs +++ b/test/Microsoft.AspNetCore.Sockets.Tests/HttpConnectionDispatcherTests.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.IO; using System.IO.Pipelines; +using System.Linq; +using System.Net; using System.Net.WebSockets; using System.Security.Claims; using System.Text; @@ -336,6 +338,90 @@ public async Task PostSendsToConnection(TransportType transportType) } } + [Fact] + public async Task HttpContextFeatureForLongpollingWorksBetweenPolls() + { + using (StartLog(out var loggerFactory, LogLevel.Debug)) + { + var manager = CreateConnectionManager(loggerFactory); + var dispatcher = new HttpConnectionDispatcher(manager, loggerFactory); + var connection = manager.CreateConnection(); + + using (var requestBody = new MemoryStream()) + using (var responseBody = new MemoryStream()) + { + var context = new DefaultHttpContext(); + context.Request.Body = requestBody; + context.Response.Body = responseBody; + + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddOptions(); + + // Setup state on the HttpContext + context.Request.Path = "/foo"; + context.Request.Method = "GET"; + var values = new Dictionary(); + values["id"] = connection.ConnectionId; + values["another"] = "value"; + var qs = new QueryCollection(values); + context.Request.Query = qs; + context.Request.Headers["header1"] = "h1"; + context.Request.Headers["header2"] = "h2"; + context.Request.Headers["header3"] = "h3"; + context.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim("claim1", "claimValue") })); + context.TraceIdentifier = "requestid"; + context.Connection.Id = "connectionid"; + context.Connection.LocalIpAddress = IPAddress.Loopback; + context.Connection.LocalPort = 4563; + context.Connection.RemoteIpAddress = IPAddress.IPv6Any; + context.Connection.RemotePort = 43456; + + var builder = new ConnectionBuilder(services.BuildServiceProvider()); + builder.UseEndPoint(); + var app = builder.Build(); + + // Start a poll + var task = dispatcher.ExecuteAsync(context, new HttpSocketOptions(), app); + + // Send to the application + var buffer = Encoding.UTF8.GetBytes("Hello World"); + await connection.Application.Output.WriteAsync(buffer); + + // The poll request should end + await task; + + // Now do a new send again without the poll (that request should have ended) + await connection.Application.Output.WriteAsync(buffer); + + connection.Application.Output.Complete(); + + // Wait for the endpoint to end + await connection.ApplicationTask; + + var connectionHttpContext = connection.GetHttpContext(); + Assert.NotNull(connectionHttpContext); + + Assert.Equal(2, connectionHttpContext.Request.Query.Count); + Assert.Equal(connection.ConnectionId, connectionHttpContext.Request.Query["id"]); + Assert.Equal("value", connectionHttpContext.Request.Query["another"]); + + Assert.Equal(3, connectionHttpContext.Request.Headers.Count); + Assert.Equal("h1", connectionHttpContext.Request.Headers["header1"]); + Assert.Equal("h2", connectionHttpContext.Request.Headers["header2"]); + Assert.Equal("h3", connectionHttpContext.Request.Headers["header3"]); + Assert.Equal("requestid", connectionHttpContext.TraceIdentifier); + Assert.Equal("claimValue", connectionHttpContext.User.Claims.FirstOrDefault().Value); + Assert.Equal("connectionid", connectionHttpContext.Connection.Id); + Assert.Equal(IPAddress.Loopback, connectionHttpContext.Connection.LocalIpAddress); + Assert.Equal(4563, connectionHttpContext.Connection.LocalPort); + Assert.Equal(IPAddress.IPv6Any, connectionHttpContext.Connection.RemoteIpAddress); + Assert.Equal(43456, connectionHttpContext.Connection.RemotePort); + Assert.Null(connectionHttpContext.RequestServices); + } + } + } + [Theory] [InlineData(TransportType.ServerSentEvents)] [InlineData(TransportType.LongPolling)] @@ -1392,6 +1478,36 @@ public override Task OnConnectedAsync(ConnectionContext connection) } } + public class HttpContextEndPoint : EndPoint + { + public override async Task OnConnectedAsync(ConnectionContext connection) + { + while (true) + { + var result = await connection.Transport.Input.ReadAsync(); + + try + { + if (result.IsCompleted) + { + break; + } + + // Make sure we have an http context + var context = connection.GetHttpContext(); + Assert.NotNull(context); + + // Echo the results + await connection.Transport.Output.WriteAsync(result.Buffer.ToArray()); + } + finally + { + connection.Transport.Input.AdvanceTo(result.Buffer.End); + } + } + } + } + public class TestEndPoint : EndPoint { public override async Task OnConnectedAsync(ConnectionContext connection) From ee12295543621c18450c386a90d7d40cd2c8b6c0 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 21 Mar 2018 23:19:46 -0700 Subject: [PATCH 2/8] Fixed tests --- .../HttpConnectionDispatcher.cs | 14 +++++++++----- .../HttpConnectionDispatcherTests.cs | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs index 0a4ce9be74..b7ba285f2f 100644 --- a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs +++ b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs @@ -543,11 +543,15 @@ private static HttpContext CloneHttpContext(HttpContext context) var existingConnectionFeature = context.Features.Get(); var connectionFeature = new HttpConnectionFeature(); - connectionFeature.ConnectionId = existingConnectionFeature.ConnectionId; - connectionFeature.LocalIpAddress = existingConnectionFeature.LocalIpAddress; - connectionFeature.LocalPort = existingConnectionFeature.LocalPort; - connectionFeature.RemoteIpAddress = existingConnectionFeature.RemoteIpAddress; - connectionFeature.RemotePort = existingConnectionFeature.RemotePort; + + if (existingConnectionFeature != null) + { + connectionFeature.ConnectionId = existingConnectionFeature.ConnectionId; + connectionFeature.LocalIpAddress = existingConnectionFeature.LocalIpAddress; + connectionFeature.LocalPort = existingConnectionFeature.LocalPort; + connectionFeature.RemoteIpAddress = existingConnectionFeature.RemoteIpAddress; + connectionFeature.RemotePort = existingConnectionFeature.RemotePort; + } // The response is a dud, you can't do anything with it anyways var responseFeature = new HttpResponseFeature(); diff --git a/test/Microsoft.AspNetCore.Sockets.Tests/HttpConnectionDispatcherTests.cs b/test/Microsoft.AspNetCore.Sockets.Tests/HttpConnectionDispatcherTests.cs index 54815a6ae2..5ac0053032 100644 --- a/test/Microsoft.AspNetCore.Sockets.Tests/HttpConnectionDispatcherTests.cs +++ b/test/Microsoft.AspNetCore.Sockets.Tests/HttpConnectionDispatcherTests.cs @@ -417,7 +417,7 @@ public async Task HttpContextFeatureForLongpollingWorksBetweenPolls() Assert.Equal(4563, connectionHttpContext.Connection.LocalPort); Assert.Equal(IPAddress.IPv6Any, connectionHttpContext.Connection.RemoteIpAddress); Assert.Equal(43456, connectionHttpContext.Connection.RemotePort); - Assert.Null(connectionHttpContext.RequestServices); + Assert.NotNull(connectionHttpContext.RequestServices); } } } @@ -798,7 +798,7 @@ public async Task ConnectionStateSetToInactiveAfterPoll() await task; Assert.Equal(DefaultConnectionContext.ConnectionStatus.Inactive, connection.Status); - Assert.Null(connection.GetHttpContext()); + Assert.NotNull(connection.GetHttpContext()); Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); } From ce624a06d8bca4ab0bb4849fc14d00b09aff347b Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 21 Mar 2018 23:37:01 -0700 Subject: [PATCH 3/8] More clean up - Set the current TraceIdentifier on the fake HttpContext. The trace identifier will reflect accurately in the logs. - Unskip tests --- .../HttpConnectionDispatcher.cs | 8 +++++++- .../HubConnectionTests.cs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs index b7ba285f2f..d952fb568b 100644 --- a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs +++ b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs @@ -501,11 +501,17 @@ private async Task EnsureConnectionStateAsync(DefaultConnectionContext con // to make the IHttpContextFeature work well, we make a copy of the relevant properties // to a new HttpContext. This means that it's impossible to affect the context // with subsequent requests. - if (connection.GetHttpContext() == null) + var existing = connection.GetHttpContext(); + if (existing == null) { var httpContext = CloneHttpContext(context); connection.SetHttpContext(httpContext); } + else + { + // Set the request trace identifier to the current http request handling the poll + existing.TraceIdentifier = context.TraceIdentifier; + } } else { diff --git a/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/HubConnectionTests.cs b/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/HubConnectionTests.cs index 5e03d870ce..c9ef03fa5c 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/HubConnectionTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/HubConnectionTests.cs @@ -692,7 +692,7 @@ public async Task ClientCanUseJwtBearerTokenForAuthentication(TransportType tran } } - [Theory(Skip = "HttpContext + Long Polling fails. Issue logged - https://github.com/aspnet/SignalR/issues/1644")] + [Theory] [MemberData(nameof(TransportTypes))] public async Task ClientCanSendHeaders(TransportType transportType) { From b18924e762782fbeb212e0bfad33231b2691181f Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 21 Mar 2018 23:43:41 -0700 Subject: [PATCH 4/8] Fixed typo --- .../HttpConnectionDispatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs index d952fb568b..10f7b1b3fc 100644 --- a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs +++ b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs @@ -568,7 +568,7 @@ private static HttpContext CloneHttpContext(HttpContext context) features.Set(connectionFeature); // REVIEW: We could strategically look at adding other features but it might be better - // if the expose a callback that would allow the user to preserve HttpContext properties. + // if we expose a callback that would allow the user to preserve HttpContext properties. var newHttpContext = new DefaultHttpContext(features); newHttpContext.TraceIdentifier = context.TraceIdentifier; From 45107ec7bfb7ec01f839009425f45381910ff42b Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 21 Mar 2018 23:48:04 -0700 Subject: [PATCH 5/8] Ignore close message --- .../HubEndpointTests.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/HubEndpointTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/HubEndpointTests.cs index 588432ea5e..b3cd2b5ff0 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/HubEndpointTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/HubEndpointTests.cs @@ -540,7 +540,17 @@ public async Task HubMethodDoesNotSendResultWhenInvocationIsNonBlocking() client.Dispose(); // Ensure the client channel is empty - Assert.Null(client.TryRead()); + var message = client.TryRead(); + switch (message) + { + case CloseMessage close: + break; + case null: + break; + default: + Assert.Null(message); + break; + } await endPointTask.OrTimeout(); } From 41076c4b127b18a40da8c19a0fabb8901dccd5ee Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 22 Mar 2018 00:22:18 -0700 Subject: [PATCH 6/8] PR feedback --- .../HttpConnectionDispatcher.cs | 6 +++--- .../HubEndpointTests.cs | 2 -- .../HttpConnectionDispatcherTests.cs | 9 +++++++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs index 10f7b1b3fc..3e7a725697 100644 --- a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs +++ b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using System.IO; using System.IO.Pipelines; -using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -575,11 +574,12 @@ private static HttpContext CloneHttpContext(HttpContext context) newHttpContext.User = context.User; // Making request services function property could be tricky and expensive as it would require - // DI scope per connection. + // DI scope per connection. It would also mean that services resolved in middleware leading up to here + // wouldn't be the same instance (but maybe that's fine). For now, we just return an empty service provider newHttpContext.RequestServices = EmptyServiceProvider.Instance; // REVIEW: This extends the lifetime of anything that got put into HttpContext.Items - newHttpContext.Items = context.Items.ToDictionary(p => p.Key, p => p.Value); + newHttpContext.Items = new Dictionary(context.Items); return newHttpContext; } diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/HubEndpointTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/HubEndpointTests.cs index b3cd2b5ff0..698c87215c 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/HubEndpointTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/HubEndpointTests.cs @@ -545,8 +545,6 @@ public async Task HubMethodDoesNotSendResultWhenInvocationIsNonBlocking() { case CloseMessage close: break; - case null: - break; default: Assert.Null(message); break; diff --git a/test/Microsoft.AspNetCore.Sockets.Tests/HttpConnectionDispatcherTests.cs b/test/Microsoft.AspNetCore.Sockets.Tests/HttpConnectionDispatcherTests.cs index 5ac0053032..84dfa2bfe8 100644 --- a/test/Microsoft.AspNetCore.Sockets.Tests/HttpConnectionDispatcherTests.cs +++ b/test/Microsoft.AspNetCore.Sockets.Tests/HttpConnectionDispatcherTests.cs @@ -391,6 +391,9 @@ public async Task HttpContextFeatureForLongpollingWorksBetweenPolls() // The poll request should end await task; + // Make sure the actual response isn't affected + Assert.Equal("application/octet-stream", context.Response.ContentType); + // Now do a new send again without the poll (that request should have ended) await connection.Application.Output.WriteAsync(buffer); @@ -418,6 +421,9 @@ public async Task HttpContextFeatureForLongpollingWorksBetweenPolls() Assert.Equal(IPAddress.IPv6Any, connectionHttpContext.Connection.RemoteIpAddress); Assert.Equal(43456, connectionHttpContext.Connection.RemotePort); Assert.NotNull(connectionHttpContext.RequestServices); + Assert.Equal(Stream.Null, connectionHttpContext.Response.Body); + Assert.NotNull(connectionHttpContext.Response.Headers); + Assert.Equal("application/xml", connectionHttpContext.Response.ContentType); } } } @@ -1497,6 +1503,9 @@ public override async Task OnConnectedAsync(ConnectionContext connection) var context = connection.GetHttpContext(); Assert.NotNull(context); + // Setting the response headers should have no effect + context.Response.ContentType = "application/xml"; + // Echo the results await connection.Transport.Output.WriteAsync(result.Buffer.ToArray()); } From c7ca72d6bf7371367d2af855ce65b4f6a8e876b9 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 22 Mar 2018 14:34:17 -0700 Subject: [PATCH 7/8] Update the user --- .../HttpConnectionDispatcher.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs index 3e7a725697..15cfb648ab 100644 --- a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs +++ b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs @@ -510,6 +510,7 @@ private async Task EnsureConnectionStateAsync(DefaultConnectionContext con { // Set the request trace identifier to the current http request handling the poll existing.TraceIdentifier = context.TraceIdentifier; + existing.User = context.User; } } else From ffb0879d40768d3b22f40430a365b8fb82bbeaef Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 22 Mar 2018 14:37:22 -0700 Subject: [PATCH 8/8] Fixed type --- .../HttpConnectionDispatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs index 15cfb648ab..595012cc57 100644 --- a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs +++ b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs @@ -497,7 +497,7 @@ private async Task EnsureConnectionStateAsync(DefaultConnectionContext con connection.Features.Set(new ConnectionInherentKeepAliveFeature(options.LongPolling.PollTimeout)); // For long polling, the requests come and go but the connection is still alive. - // to make the IHttpContextFeature work well, we make a copy of the relevant properties + // To make the IHttpContextFeature work well, we make a copy of the relevant properties // to a new HttpContext. This means that it's impossible to affect the context // with subsequent requests. var existing = connection.GetHttpContext();