private async Task ProcessNegotiate(HttpContext context, HttpConnectionDispatcherOptions options, ConnectionLogScope logScope) { context.Response.ContentType = "application/json"; string error = null; int clientProtocolVersion = 0; 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 clientProtocolVersion)) { error = $"The client requested an invalid protocol version '{queryStringVersionValue}'"; Log.InvalidNegotiateProtocolVersion(_logger, queryStringVersionValue); } else if (clientProtocolVersion < options.MinimumProtocolVersion) { error = $"The client requested version '{clientProtocolVersion}', but the server does not support this version."; Log.NegotiateProtocolVersionMismatch(_logger, clientProtocolVersion); } else if (clientProtocolVersion > _protocolVersion) { clientProtocolVersion = _protocolVersion; } } else if (options.MinimumProtocolVersion > 0) { // NegotiateVersion wasn't parsed meaning the client requests version 0. error = $"The client requested version '0', but the server does not support this version."; Log.NegotiateProtocolVersionMismatch(_logger, 0); } // Establish the connection HttpConnectionContext connection = null; if (error == null) { connection = CreateConnection(options, clientProtocolVersion); } // Set the Connection ID on the logging scope so that logs from now on will have the // Connection ID metadata set. logScope.ConnectionId = connection?.ConnectionId; // Don't use thread static instance here because writer is used with async var writer = new MemoryBufferWriter(); try { // Get the bytes for the connection id WriteNegotiatePayload(writer, connection?.ConnectionId, connection?.ConnectionToken, context, options, clientProtocolVersion, error); Log.NegotiationRequest(_logger); // Write it out to the response with the right content length context.Response.ContentLength = writer.Length; await writer.CopyToAsync(context.Response.Body); } finally { writer.Reset(); } }
private async Task <bool> EnsureConnectionStateAsync(HttpConnectionContext connection, HttpContext context, HttpTransportType transportType, HttpTransportType supportedTransports, ConnectionLogScope logScope, HttpConnectionDispatcherOptions options) { if ((supportedTransports & transportType) == 0) { context.Response.ContentType = "text/plain"; context.Response.StatusCode = StatusCodes.Status404NotFound; Log.TransportNotSupported(_logger, transportType); await context.Response.WriteAsync($"{transportType} transport not supported by this end point type"); return(false); } // Set the IHttpConnectionFeature now that we can access it. connection.Features.Set(context.Features.Get <IHttpConnectionFeature>()); if (connection.TransportType == HttpTransportType.None) { connection.TransportType = transportType; } else if (connection.TransportType != transportType) { context.Response.ContentType = "text/plain"; context.Response.StatusCode = StatusCodes.Status400BadRequest; Log.CannotChangeTransport(_logger, connection.TransportType, transportType); await context.Response.WriteAsync("Cannot change transports mid-connection"); return(false); } // Configure transport-specific features. if (transportType == HttpTransportType.LongPolling) { connection.HasInherentKeepAlive = true; // 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. var existing = connection.HttpContext; if (existing == null) { var httpContext = CloneHttpContext(context); connection.HttpContext = httpContext; } else { // Set the request trace identifier to the current http request handling the poll existing.TraceIdentifier = context.TraceIdentifier; // Don't copy the identity if it's a windows identity // We specifically clone the identity on first poll if it's a windows identity // If we swapped the new User here we'd have to dispose the old identities which could race with the application // trying to access the identity. if (!(context.User.Identity is WindowsIdentity)) { existing.User = context.User; } } } else { connection.HttpContext = context; } // Setup the connection state from the http context connection.User = connection.HttpContext.User; // Set the Connection ID on the logging scope so that logs from now on will have the // Connection ID metadata set. logScope.ConnectionId = connection.ConnectionId; return(true); }
private static void CloneHttpContext(HttpContext context, HttpConnectionContext connection) { // 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 <IHttpRequestFeature>(); 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 <string, StringValues>(existingRequestFeature.Headers.Count, StringComparer.OrdinalIgnoreCase); foreach (var header in existingRequestFeature.Headers) { requestHeaders[header.Key] = header.Value; } requestFeature.Headers = new HeaderDictionary(requestHeaders); var existingConnectionFeature = context.Features.Get <IHttpConnectionFeature>(); var connectionFeature = new HttpConnectionFeature(); 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(); var features = new FeatureCollection(); features.Set <IHttpRequestFeature>(requestFeature); features.Set <IHttpResponseFeature>(responseFeature); features.Set <IHttpResponseBodyFeature>(new StreamResponseBodyFeature(Stream.Null)); features.Set <IHttpConnectionFeature>(connectionFeature); // REVIEW: We could strategically look at adding other features but it might be better // if we expose a callback that would allow the user to preserve HttpContext properties. var newHttpContext = new DefaultHttpContext(features); newHttpContext.TraceIdentifier = context.TraceIdentifier; var endpointFeature = context.Features.Get <IEndpointFeature>(); newHttpContext.SetEndpoint(endpointFeature?.Endpoint); CloneUser(newHttpContext, context); connection.ServiceScope = context.RequestServices.CreateScope(); newHttpContext.RequestServices = connection.ServiceScope.ServiceProvider; // REVIEW: This extends the lifetime of anything that got put into HttpContext.Items newHttpContext.Items = new Dictionary <object, object>(context.Items); connection.HttpContext = newHttpContext; }