Handle incoming HTTP requests being canceled gracefully (#2314)
This commit is contained in:
parent
bff2060454
commit
96bbe70cd8
|
|
@ -49,6 +49,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
|
||||||
private static readonly Action<ILogger, string, Exception> _connectionDisposedWhileWriteInProgress =
|
private static readonly Action<ILogger, string, Exception> _connectionDisposedWhileWriteInProgress =
|
||||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(13, "ConnectionDisposedWhileWriteInProgress"), "Connection {TransportConnectionId} was disposed while a write was in progress.");
|
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(13, "ConnectionDisposedWhileWriteInProgress"), "Connection {TransportConnectionId} was disposed while a write was in progress.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, Exception> _failedToReadHttpRequestBody =
|
||||||
|
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(14, "FailedToReadHttpRequestBody"), "Connection {TransportConnectionId} failed to read the HTTP request body.");
|
||||||
|
|
||||||
public static void ConnectionDisposed(ILogger logger, string connectionId)
|
public static void ConnectionDisposed(ILogger logger, string connectionId)
|
||||||
{
|
{
|
||||||
_connectionDisposed(logger, connectionId, null);
|
_connectionDisposed(logger, connectionId, null);
|
||||||
|
|
@ -113,6 +116,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
|
||||||
{
|
{
|
||||||
_connectionDisposedWhileWriteInProgress(logger, connectionId, ex);
|
_connectionDisposedWhileWriteInProgress(logger, connectionId, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void FailedToReadHttpRequestBody(ILogger logger, string connectionId, Exception ex)
|
||||||
|
{
|
||||||
|
_failedToReadHttpRequestBody(logger, connectionId, ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -505,6 +505,15 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
|
||||||
context.Response.ContentType = "text/plain";
|
context.Response.ContentType = "text/plain";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
// Can occur when the HTTP request is canceled by the client
|
||||||
|
Log.FailedToReadHttpRequestBody(_logger, connection.ConnectionId, ex);
|
||||||
|
|
||||||
|
context.Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||||
|
context.Response.ContentType = "text/plain";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Log.ReceivedBytes(_logger, connection.ApplicationStream.Length);
|
Log.ReceivedBytes(_logger, connection.ApplicationStream.Length);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Testing;
|
using Microsoft.Extensions.Logging.Testing;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using Moq;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -719,6 +720,44 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(HttpTransportType.LongPolling)]
|
||||||
|
[InlineData(HttpTransportType.ServerSentEvents)]
|
||||||
|
public async Task IOExceptionWhenReadingRequestReturns400Response(HttpTransportType transportType)
|
||||||
|
{
|
||||||
|
using (StartVerifiableLog(out var loggerFactory, LogLevel.Debug))
|
||||||
|
{
|
||||||
|
var manager = CreateConnectionManager(loggerFactory);
|
||||||
|
var dispatcher = new HttpConnectionDispatcher(manager, loggerFactory);
|
||||||
|
var connection = manager.CreateConnection();
|
||||||
|
connection.TransportType = transportType;
|
||||||
|
|
||||||
|
var mockStream = new Mock<Stream>();
|
||||||
|
mockStream.Setup(m => m.CopyToAsync(It.IsAny<Stream>(), It.IsAny<int>(), It.IsAny<CancellationToken>())).Throws(new IOException());
|
||||||
|
|
||||||
|
using (var responseBody = new MemoryStream())
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext();
|
||||||
|
context.Request.Body = mockStream.Object;
|
||||||
|
context.Response.Body = responseBody;
|
||||||
|
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddSingleton<TestConnectionHandler>();
|
||||||
|
services.AddOptions();
|
||||||
|
context.Request.Path = "/foo";
|
||||||
|
context.Request.Method = "POST";
|
||||||
|
var values = new Dictionary<string, StringValues>();
|
||||||
|
values["id"] = connection.ConnectionId;
|
||||||
|
var qs = new QueryCollection(values);
|
||||||
|
context.Request.Query = qs;
|
||||||
|
|
||||||
|
await dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), c => Task.CompletedTask);
|
||||||
|
|
||||||
|
Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task EndpointsThatRequireConnectionId400WhenNoConnectionIdProvidedForPost()
|
public async Task EndpointsThatRequireConnectionId400WhenNoConnectionIdProvidedForPost()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue