| File: Internal\Transports\LongPollingServerTransport.cs | Web Access |
| Project: src\aspnetcore\src\SignalR\common\Http.Connections\src\Microsoft.AspNetCore.Http.Connections.csproj (Microsoft.AspNetCore.Http.Connections) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.IO.Pipelines; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Http.Connections.Internal.Transports; internal sealed partial class LongPollingServerTransport : IHttpTransport { private readonly PipeReader _application; private readonly ILogger _logger; private readonly CancellationToken _timeoutToken; private readonly HttpConnectionContext? _connection; public LongPollingServerTransport(CancellationToken timeoutToken, PipeReader application, ILoggerFactory loggerFactory) : this(timeoutToken, application, loggerFactory, connection: null) { } public LongPollingServerTransport(CancellationToken timeoutToken, PipeReader application, ILoggerFactory loggerFactory, HttpConnectionContext? connection) { _timeoutToken = timeoutToken; _application = application; _connection = connection; // We create the logger with a string to preserve the logging namespace after the server side transport renames. _logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Connections.Internal.Transports.LongPollingTransport"); } public async Task<bool> ProcessRequestAsync(HttpContext context, CancellationToken token) { try { var result = await _application.ReadAsync(token); var buffer = result.Buffer; try { if (buffer.IsEmpty && (result.IsCompleted || result.IsCanceled)) { Log.LongPolling204(_logger); context.Response.ContentType = "text/plain"; context.Response.StatusCode = StatusCodes.Status204NoContent; return false; } // We're intentionally not checking cancellation here because we need to drain messages we've got so far, // but it's too late to emit the 204 required by being canceled. Log.LongPollingWritingMessage(_logger, buffer.Length); context.Response.ContentLength = buffer.Length; context.Response.ContentType = "application/octet-stream"; _connection?.StartSendCancellation(); await context.Response.Body.WriteAsync(buffer, _connection?.SendingToken ?? default); } finally { _connection?.StopSendCancellation(); _application.AdvanceTo(buffer.End); } } catch (OperationCanceledException) { // 4 cases: // 1 - Request aborted, the client disconnected (no response) // 2 - The poll timeout is hit (200) // 3 - SendingToken was canceled, abort the connection // 4 - A new request comes in and cancels this request (204) // Case 1 if (context.RequestAborted.IsCancellationRequested) { // Don't count this as cancellation, this is normal as the poll can end due to the browser closing. // The background thread will eventually dispose this connection if it's inactive Log.LongPollingDisconnected(_logger); } // Case 2 else if (_timeoutToken.IsCancellationRequested) { Log.PollTimedOut(_logger); context.Response.ContentLength = 0; context.Response.ContentType = "text/plain"; context.Response.StatusCode = StatusCodes.Status200OK; } else if (_connection?.SendingToken.IsCancellationRequested == true) { // Case 3 context.Response.ContentType = "text/plain"; context.Response.StatusCode = StatusCodes.Status204NoContent; throw; } else { // Case 4 Log.LongPolling204(_logger); context.Response.ContentType = "text/plain"; context.Response.StatusCode = StatusCodes.Status204NoContent; } } catch (Exception ex) { Log.LongPollingTerminated(_logger, ex); context.Response.ContentType = "text/plain"; context.Response.StatusCode = StatusCodes.Status500InternalServerError; throw; } return false; } private static partial class Log { [LoggerMessage(1, LogLevel.Debug, "Terminating Long Polling connection by sending 204 response.", EventName = "LongPolling204")] public static partial void LongPolling204(ILogger logger); [LoggerMessage(2, LogLevel.Debug, "Poll request timed out. Sending 200 response to connection.", EventName = "PollTimedOut")] public static partial void PollTimedOut(ILogger logger); [LoggerMessage(3, LogLevel.Trace, "Writing a {Count} byte message to connection.", EventName = "LongPollingWritingMessage")] public static partial void LongPollingWritingMessage(ILogger logger, long count); [LoggerMessage(4, LogLevel.Debug, "Client disconnected from Long Polling endpoint for connection.", EventName = "LongPollingDisconnected")] public static partial void LongPollingDisconnected(ILogger logger); [LoggerMessage(5, LogLevel.Error, "Long Polling transport was terminated due to an error on connection.", EventName = "LongPollingTerminated")] public static partial void LongPollingTerminated(ILogger logger, Exception ex); } }