Rewrite Listener.ResponseSendFileTests
This commit is contained in:
Родитель
7974467dc9
Коммит
2451b68ca0
|
@ -1,344 +0,0 @@
|
|||
// 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.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.HttpSys.Listener
|
||||
{
|
||||
public class ResponseSendFileTests
|
||||
{
|
||||
private readonly string AbsoluteFilePath;
|
||||
private readonly string RelativeFilePath;
|
||||
private readonly long FileLength;
|
||||
|
||||
public ResponseSendFileTests()
|
||||
{
|
||||
AbsoluteFilePath = Directory.GetFiles(Directory.GetCurrentDirectory()).First();
|
||||
RelativeFilePath = Path.GetFileName(AbsoluteFilePath);
|
||||
FileLength = new FileInfo(AbsoluteFilePath).Length;
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFile_EmptyFileCountUnspecified_SetsChunkedAndFlushesHeaders()
|
||||
{
|
||||
var emptyFilePath = Path.Combine(Directory.GetCurrentDirectory(), "zz_" + Guid.NewGuid().ToString() + "EmptyTestFile.txt");
|
||||
var emptyFile = File.Create(emptyFilePath, 1024);
|
||||
emptyFile.Dispose();
|
||||
|
||||
string address;
|
||||
using (var server = Utilities.CreateHttpServer(out address))
|
||||
{
|
||||
var responseTask = SendRequestAsync(address);
|
||||
|
||||
var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
|
||||
await context.Response.SendFileAsync(emptyFilePath, 0, null, CancellationToken.None);
|
||||
Assert.True(context.Response.HasStarted);
|
||||
await context.Response.Body.WriteAsync(new byte[10], 0, 10, CancellationToken.None);
|
||||
context.Dispose();
|
||||
File.Delete(emptyFilePath);
|
||||
|
||||
var response = await responseTask;
|
||||
Assert.Equal(200, (int)response.StatusCode);
|
||||
IEnumerable<string> contentLength;
|
||||
Assert.False(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
|
||||
Assert.True(response.Headers.TransferEncodingChunked.HasValue);
|
||||
Assert.Equal(10, (await response.Content.ReadAsByteArrayAsync()).Length);
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFile_WithActiveCancellationToken_Success()
|
||||
{
|
||||
string address;
|
||||
using (var server = Utilities.CreateHttpServer(out address))
|
||||
{
|
||||
var responseTask = SendRequestAsync(address);
|
||||
|
||||
var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
|
||||
var cts = new CancellationTokenSource();
|
||||
// First write sends headers
|
||||
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
context.Dispose();
|
||||
|
||||
var response = await responseTask;
|
||||
Assert.Equal(200, (int)response.StatusCode);
|
||||
Assert.Equal(FileLength * 2, (await response.Content.ReadAsByteArrayAsync()).Length);
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFile_WithTimerCancellationToken_Success()
|
||||
{
|
||||
string address;
|
||||
using (var server = Utilities.CreateHttpServer(out address))
|
||||
{
|
||||
var responseTask = SendRequestAsync(address);
|
||||
|
||||
var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(TimeSpan.FromSeconds(10));
|
||||
// First write sends headers
|
||||
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
context.Dispose();
|
||||
|
||||
var response = await responseTask;
|
||||
Assert.Equal(200, (int)response.StatusCode);
|
||||
Assert.Equal(FileLength * 2, (await response.Content.ReadAsByteArrayAsync()).Length);
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFileWriteExceptions_FirstCallWithCanceledCancellationToken_CancelsAndAborts()
|
||||
{
|
||||
string address;
|
||||
using (var server = Utilities.CreateHttpServer(out address))
|
||||
{
|
||||
server.Options.ThrowWriteExceptions = true;
|
||||
var responseTask = SendRequestAsync(address);
|
||||
|
||||
var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
// First write sends headers
|
||||
var writeTask = context.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
Assert.True(writeTask.IsCanceled);
|
||||
context.Dispose();
|
||||
#if NET461
|
||||
// .NET HttpClient automatically retries a request if it does not get a response.
|
||||
context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
|
||||
cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
// First write sends headers
|
||||
writeTask = context.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
Assert.True(writeTask.IsCanceled);
|
||||
context.Dispose();
|
||||
#elif NETCOREAPP2_2
|
||||
#else
|
||||
#error Target framework needs to be updated
|
||||
#endif
|
||||
await Assert.ThrowsAsync<HttpRequestException>(() => responseTask);
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFile_FirstSendWithCanceledCancellationToken_CancelsAndAborts()
|
||||
{
|
||||
string address;
|
||||
using (var server = Utilities.CreateHttpServer(out address))
|
||||
{
|
||||
var responseTask = SendRequestAsync(address);
|
||||
|
||||
var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
// First write sends headers
|
||||
var writeTask = context.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
Assert.True(writeTask.IsCanceled);
|
||||
context.Dispose();
|
||||
#if NET461
|
||||
// .NET HttpClient automatically retries a request if it does not get a response.
|
||||
context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
|
||||
cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
// First write sends headers
|
||||
writeTask = context.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
Assert.True(writeTask.IsCanceled);
|
||||
context.Dispose();
|
||||
#elif NETCOREAPP2_2
|
||||
#else
|
||||
#error Target framework needs to be updated
|
||||
#endif
|
||||
await Assert.ThrowsAsync<HttpRequestException>(() => responseTask);
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFileExceptions_SecondSendWithCanceledCancellationToken_CancelsAndAborts()
|
||||
{
|
||||
string address;
|
||||
using (var server = Utilities.CreateHttpServer(out address))
|
||||
{
|
||||
server.Options.ThrowWriteExceptions = true;
|
||||
var responseTask = SendRequestAsync(address);
|
||||
|
||||
var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
|
||||
var cts = new CancellationTokenSource();
|
||||
// First write sends headers
|
||||
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
cts.Cancel();
|
||||
var writeTask = context.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
Assert.True(writeTask.IsCanceled);
|
||||
context.Dispose();
|
||||
|
||||
await Assert.ThrowsAsync<HttpRequestException>(() => responseTask);
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFile_SecondSendWithCanceledCancellationToken_CancelsAndAborts()
|
||||
{
|
||||
string address;
|
||||
using (var server = Utilities.CreateHttpServer(out address))
|
||||
{
|
||||
var responseTask = SendRequestAsync(address);
|
||||
|
||||
var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
|
||||
var cts = new CancellationTokenSource();
|
||||
// First write sends headers
|
||||
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
cts.Cancel();
|
||||
var writeTask = context.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
Assert.True(writeTask.IsCanceled);
|
||||
context.Dispose();
|
||||
|
||||
await Assert.ThrowsAsync<HttpRequestException>(() => responseTask);
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFileExceptions_ClientDisconnectsBeforeFirstSend_SendThrows()
|
||||
{
|
||||
string address;
|
||||
using (var server = Utilities.CreateHttpServer(out address))
|
||||
{
|
||||
server.Options.ThrowWriteExceptions = true;
|
||||
var cts = new CancellationTokenSource();
|
||||
var responseTask = SendRequestAsync(address, cts.Token);
|
||||
|
||||
var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
|
||||
|
||||
// First write sends headers
|
||||
cts.Cancel();
|
||||
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => responseTask);
|
||||
|
||||
Assert.True(context.DisconnectToken.WaitHandle.WaitOne(TimeSpan.FromSeconds(5)));
|
||||
await Assert.ThrowsAsync<IOException>(async () =>
|
||||
{
|
||||
// It can take several tries before Send notices the disconnect.
|
||||
for (int i = 0; i < Utilities.WriteRetryLimit; i++)
|
||||
{
|
||||
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
|
||||
}
|
||||
});
|
||||
|
||||
await Assert.ThrowsAsync<ObjectDisposedException>(() =>
|
||||
context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None));
|
||||
|
||||
context.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFile_ClientDisconnectsBeforeFirstSend_SendCompletesSilently()
|
||||
{
|
||||
string address;
|
||||
using (var server = Utilities.CreateHttpServer(out address))
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
var responseTask = SendRequestAsync(address, cts.Token);
|
||||
|
||||
var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
|
||||
// First write sends headers
|
||||
cts.Cancel();
|
||||
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => responseTask);
|
||||
Assert.True(context.DisconnectToken.WaitHandle.WaitOne(TimeSpan.FromSeconds(5)));
|
||||
// It can take several tries before Send notices the disconnect.
|
||||
for (int i = 0; i < Utilities.WriteRetryLimit; i++)
|
||||
{
|
||||
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
|
||||
}
|
||||
context.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFileExceptions_ClientDisconnectsBeforeSecondSend_SendThrows()
|
||||
{
|
||||
string address;
|
||||
using (var server = Utilities.CreateHttpServer(out address))
|
||||
{
|
||||
server.Options.ThrowWriteExceptions = true;
|
||||
RequestContext context;
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
var responseTask = client.GetAsync(address, HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
|
||||
// First write sends headers
|
||||
var sendFileTask = context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
|
||||
|
||||
var response = await responseTask;
|
||||
response.EnsureSuccessStatusCode();
|
||||
// Drain data from the connection so that SendFileAsync can complete.
|
||||
var bufferTask = response.Content.LoadIntoBufferAsync();
|
||||
|
||||
await sendFileTask;
|
||||
response.Dispose();
|
||||
}
|
||||
|
||||
Assert.True(context.DisconnectToken.WaitHandle.WaitOne(TimeSpan.FromSeconds(5)));
|
||||
await Assert.ThrowsAsync<IOException>(async () =>
|
||||
{
|
||||
// It can take several tries before Write notices the disconnect.
|
||||
for (int i = 0; i < Utilities.WriteRetryLimit; i++)
|
||||
{
|
||||
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
|
||||
}
|
||||
});
|
||||
context.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFile_ClientDisconnectsBeforeSecondSend_SendCompletesSilently()
|
||||
{
|
||||
string address;
|
||||
using (var server = Utilities.CreateHttpServer(out address))
|
||||
{
|
||||
RequestContext context;
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
var responseTask = client.GetAsync(address, HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
|
||||
// First write sends headers
|
||||
var sendFileTask = context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
|
||||
|
||||
var response = await responseTask;
|
||||
response.EnsureSuccessStatusCode();
|
||||
// Drain data from the connection so that SendFileAsync can complete.
|
||||
var bufferTask = response.Content.LoadIntoBufferAsync();
|
||||
|
||||
await sendFileTask;
|
||||
response.Dispose();
|
||||
}
|
||||
|
||||
Assert.True(context.DisconnectToken.WaitHandle.WaitOne(TimeSpan.FromSeconds(5)));
|
||||
// It can take several tries before Write notices the disconnect.
|
||||
for (int i = 0; i < Utilities.WriteRetryLimit; i++)
|
||||
{
|
||||
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
|
||||
}
|
||||
context.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> SendRequestAsync(string uri, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
using (HttpClient client = new HttpClient())
|
||||
{
|
||||
return await client.GetAsync(uri, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -134,7 +134,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
ManualResetEvent waitHandle = new ManualResetEvent(false);
|
||||
bool? upgraded = null;
|
||||
string address;
|
||||
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, async httpContext =>
|
||||
using (Utilities.CreateHttpServer(out address, async httpContext =>
|
||||
{
|
||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.NotNull(feature);
|
||||
|
@ -152,7 +152,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
Assert.Equal(15, await stream.ReadAsync(new byte[15], 0, 15));
|
||||
upgraded = true;
|
||||
waitHandle.Set();
|
||||
}))
|
||||
}, options => options.MaxRequestBodySize = 10))
|
||||
{
|
||||
using (Stream stream = await SendOpaqueRequestAsync("GET", address))
|
||||
{
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
public async Task ContentLengthEqualsLimit_ReadSync_Success()
|
||||
{
|
||||
string address;
|
||||
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, httpContext =>
|
||||
using (Utilities.CreateHttpServer(out address, httpContext =>
|
||||
{
|
||||
httpContext.Features.Get<IHttpBodyControlFeature>().AllowSynchronousIO = true;
|
||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
|
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
httpContext.Response.ContentLength = read;
|
||||
httpContext.Response.Body.Write(input, 0, read);
|
||||
return Task.FromResult(0);
|
||||
}))
|
||||
}, options => options.MaxRequestBodySize = 11))
|
||||
{
|
||||
var response = await SendRequestAsync(address, "Hello World");
|
||||
Assert.Equal("Hello World", response);
|
||||
|
@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
public async Task ContentLengthEqualsLimit_ReadAsync_Success()
|
||||
{
|
||||
string address;
|
||||
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, async httpContext =>
|
||||
using (Utilities.CreateHttpServer(out address, async httpContext =>
|
||||
{
|
||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.NotNull(feature);
|
||||
|
@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
|
||||
httpContext.Response.ContentLength = read;
|
||||
await httpContext.Response.Body.WriteAsync(input, 0, read);
|
||||
}))
|
||||
}, options => options.MaxRequestBodySize = 11))
|
||||
{
|
||||
var response = await SendRequestAsync(address, "Hello World");
|
||||
Assert.Equal("Hello World", response);
|
||||
|
@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
public async Task ContentLengthEqualsLimit_ReadBeginEnd_Success()
|
||||
{
|
||||
string address;
|
||||
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, httpContext =>
|
||||
using (Utilities.CreateHttpServer(out address, httpContext =>
|
||||
{
|
||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.NotNull(feature);
|
||||
|
@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
httpContext.Response.ContentLength = read;
|
||||
httpContext.Response.Body.EndWrite(httpContext.Response.Body.BeginWrite(input, 0, read, null, null));
|
||||
return Task.FromResult(0);
|
||||
}))
|
||||
}, options => options.MaxRequestBodySize = 11))
|
||||
{
|
||||
var response = await SendRequestAsync(address, "Hello World");
|
||||
Assert.Equal("Hello World", response);
|
||||
|
@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
public async Task ChunkedEqualsLimit_ReadSync_Success()
|
||||
{
|
||||
string address;
|
||||
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, httpContext =>
|
||||
using (Utilities.CreateHttpServer(out address, httpContext =>
|
||||
{
|
||||
httpContext.Features.Get<IHttpBodyControlFeature>().AllowSynchronousIO = true;
|
||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
|
@ -97,7 +97,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
httpContext.Response.ContentLength = read;
|
||||
httpContext.Response.Body.Write(input, 0, read);
|
||||
return Task.FromResult(0);
|
||||
}))
|
||||
}, options => options.MaxRequestBodySize = 11))
|
||||
{
|
||||
var response = await SendRequestAsync(address, "Hello World", chunked: true);
|
||||
Assert.Equal("Hello World", response);
|
||||
|
@ -108,7 +108,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
public async Task ChunkedEqualsLimit_ReadAsync_Success()
|
||||
{
|
||||
string address;
|
||||
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, async httpContext =>
|
||||
using (Utilities.CreateHttpServer(out address, async httpContext =>
|
||||
{
|
||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.NotNull(feature);
|
||||
|
@ -118,7 +118,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
|
||||
httpContext.Response.ContentLength = read;
|
||||
await httpContext.Response.Body.WriteAsync(input, 0, read);
|
||||
}))
|
||||
}, options => options.MaxRequestBodySize = 11))
|
||||
{
|
||||
var response = await SendRequestAsync(address, "Hello World", chunked: true);
|
||||
Assert.Equal("Hello World", response);
|
||||
|
@ -129,7 +129,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
public async Task ChunkedEqualsLimit_ReadBeginEnd_Success()
|
||||
{
|
||||
string address;
|
||||
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, httpContext =>
|
||||
using (Utilities.CreateHttpServer(out address, httpContext =>
|
||||
{
|
||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.NotNull(feature);
|
||||
|
@ -140,7 +140,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
httpContext.Response.ContentLength = read;
|
||||
httpContext.Response.Body.EndWrite(httpContext.Response.Body.BeginWrite(input, 0, read, null, null));
|
||||
return Task.FromResult(0);
|
||||
}))
|
||||
}, options => options.MaxRequestBodySize = 11))
|
||||
{
|
||||
var response = await SendRequestAsync(address, "Hello World", chunked: true);
|
||||
Assert.Equal("Hello World", response);
|
||||
|
@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
public async Task ContentLengthExceedsLimit_ReadSync_ThrowsImmediately()
|
||||
{
|
||||
string address;
|
||||
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, httpContext =>
|
||||
using (Utilities.CreateHttpServer(out address, httpContext =>
|
||||
{
|
||||
httpContext.Features.Get<IHttpBodyControlFeature>().AllowSynchronousIO = true;
|
||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
|
@ -164,7 +164,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
ex = Assert.Throws<IOException>(() => httpContext.Request.Body.Read(input, 0, input.Length));
|
||||
Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
|
||||
return Task.FromResult(0);
|
||||
}))
|
||||
}, options => options.MaxRequestBodySize = 10))
|
||||
{
|
||||
var response = await SendRequestAsync(address, "Hello World");
|
||||
Assert.Equal(string.Empty, response);
|
||||
|
@ -175,7 +175,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
public async Task ContentLengthExceedsLimit_ReadAsync_ThrowsImmediately()
|
||||
{
|
||||
string address;
|
||||
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, httpContext =>
|
||||
using (Utilities.CreateHttpServer(out address, httpContext =>
|
||||
{
|
||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.NotNull(feature);
|
||||
|
@ -187,7 +187,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
ex = Assert.Throws<IOException>(() => { var t = httpContext.Request.Body.ReadAsync(input, 0, input.Length); });
|
||||
Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
|
||||
return Task.FromResult(0);
|
||||
}))
|
||||
}, options => options.MaxRequestBodySize = 10))
|
||||
{
|
||||
var response = await SendRequestAsync(address, "Hello World");
|
||||
Assert.Equal(string.Empty, response);
|
||||
|
@ -198,7 +198,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
public async Task ContentLengthExceedsLimit_ReadBeginEnd_ThrowsImmediately()
|
||||
{
|
||||
string address;
|
||||
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, httpContext =>
|
||||
using (Utilities.CreateHttpServer(out address, httpContext =>
|
||||
{
|
||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.NotNull(feature);
|
||||
|
@ -210,7 +210,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
ex = Assert.Throws<IOException>(() => httpContext.Request.Body.BeginRead(input, 0, input.Length, null, null));
|
||||
Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
|
||||
return Task.FromResult(0);
|
||||
}))
|
||||
}, options => options.MaxRequestBodySize = 10))
|
||||
{
|
||||
var response = await SendRequestAsync(address, "Hello World");
|
||||
Assert.Equal(string.Empty, response);
|
||||
|
@ -221,7 +221,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
public async Task ChunkedExceedsLimit_ReadSync_ThrowsAtLimit()
|
||||
{
|
||||
string address;
|
||||
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, httpContext =>
|
||||
using (Utilities.CreateHttpServer(out address, httpContext =>
|
||||
{
|
||||
httpContext.Features.Get<IHttpBodyControlFeature>().AllowSynchronousIO = true;
|
||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
|
@ -234,7 +234,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
ex = Assert.Throws<IOException>(() => httpContext.Request.Body.Read(input, 0, input.Length));
|
||||
Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
|
||||
return Task.FromResult(0);
|
||||
}))
|
||||
}, options => options.MaxRequestBodySize = 10))
|
||||
{
|
||||
var response = await SendRequestAsync(address, "Hello World", chunked: true);
|
||||
Assert.Equal(string.Empty, response);
|
||||
|
@ -245,7 +245,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
public async Task ChunkedExceedsLimit_ReadAsync_ThrowsAtLimit()
|
||||
{
|
||||
string address;
|
||||
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, async httpContext =>
|
||||
using (Utilities.CreateHttpServer(out address, async httpContext =>
|
||||
{
|
||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.NotNull(feature);
|
||||
|
@ -256,7 +256,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
|
||||
ex = await Assert.ThrowsAsync<IOException>(() => httpContext.Request.Body.ReadAsync(input, 0, input.Length));
|
||||
Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
|
||||
}))
|
||||
}, options => options.MaxRequestBodySize = 10))
|
||||
{
|
||||
var response = await SendRequestAsync(address, "Hello World", chunked: true);
|
||||
Assert.Equal(string.Empty, response);
|
||||
|
@ -267,7 +267,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
public async Task ChunkedExceedsLimit_ReadBeginEnd_ThrowsAtLimit()
|
||||
{
|
||||
string address;
|
||||
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, httpContext =>
|
||||
using (Utilities.CreateHttpServer(out address, httpContext =>
|
||||
{
|
||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.NotNull(feature);
|
||||
|
@ -280,7 +280,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
ex = Assert.Throws<IOException>(() => body.EndRead(body.BeginRead(input, 0, input.Length, null, null)));
|
||||
Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
|
||||
return Task.FromResult(0);
|
||||
}))
|
||||
}, options => options.MaxRequestBodySize = 10))
|
||||
{
|
||||
var response = await SendRequestAsync(address, "Hello World", chunked: true);
|
||||
Assert.Equal(string.Empty, response);
|
||||
|
@ -292,7 +292,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
{
|
||||
var content = new StaggardContent();
|
||||
string address;
|
||||
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, httpContext =>
|
||||
using (Utilities.CreateHttpServer(out address, httpContext =>
|
||||
{
|
||||
httpContext.Features.Get<IHttpBodyControlFeature>().AllowSynchronousIO = true;
|
||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
|
@ -306,7 +306,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
var ex = Assert.Throws<IOException>(() => httpContext.Request.Body.Read(input, 0, input.Length));
|
||||
Assert.Equal("The total number of bytes read 20 has exceeded the request body size limit 10.", ex.Message);
|
||||
return Task.FromResult(0);
|
||||
}))
|
||||
}, options => options.MaxRequestBodySize = 10))
|
||||
{
|
||||
string response = await SendRequestAsync(address, content, chunked: true);
|
||||
Assert.Equal(string.Empty, response);
|
||||
|
@ -318,7 +318,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
{
|
||||
var content = new StaggardContent();
|
||||
string address;
|
||||
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, async httpContext =>
|
||||
using (Utilities.CreateHttpServer(out address, async httpContext =>
|
||||
{
|
||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.NotNull(feature);
|
||||
|
@ -330,7 +330,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
content.Block.Release();
|
||||
var ex = await Assert.ThrowsAsync<IOException>(() => httpContext.Request.Body.ReadAsync(input, 0, input.Length));
|
||||
Assert.Equal("The total number of bytes read 20 has exceeded the request body size limit 10.", ex.Message);
|
||||
}))
|
||||
}, options => options.MaxRequestBodySize = 10))
|
||||
{
|
||||
string response = await SendRequestAsync(address, content, chunked: true);
|
||||
Assert.Equal(string.Empty, response);
|
||||
|
@ -341,7 +341,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
public async Task AdjustLimitPerRequest_ContentLength_ReadAsync_Success()
|
||||
{
|
||||
string address;
|
||||
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, async httpContext =>
|
||||
using (Utilities.CreateHttpServer(out address, async httpContext =>
|
||||
{
|
||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.NotNull(feature);
|
||||
|
@ -354,7 +354,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
Assert.True(feature.IsReadOnly);
|
||||
httpContext.Response.ContentLength = read;
|
||||
await httpContext.Response.Body.WriteAsync(input, 0, read);
|
||||
}))
|
||||
}, options => options.MaxRequestBodySize = 11))
|
||||
{
|
||||
var response = await SendRequestAsync(address, "Hello World!");
|
||||
Assert.Equal("Hello World!", response);
|
||||
|
@ -365,7 +365,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
public async Task AdjustLimitPerRequest_Chunked_ReadAsync_Success()
|
||||
{
|
||||
string address;
|
||||
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, async httpContext =>
|
||||
using (Utilities.CreateHttpServer(out address, async httpContext =>
|
||||
{
|
||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.NotNull(feature);
|
||||
|
@ -378,7 +378,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
Assert.True(feature.IsReadOnly);
|
||||
httpContext.Response.ContentLength = read;
|
||||
await httpContext.Response.Body.WriteAsync(input, 0, read);
|
||||
}))
|
||||
}, options => options.MaxRequestBodySize = 11))
|
||||
{
|
||||
var response = await SendRequestAsync(address, "Hello World!", chunked: true);
|
||||
Assert.Equal("Hello World!", response);
|
||||
|
|
|
@ -10,6 +10,7 @@ using System.Net.Http;
|
|||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Xunit;
|
||||
|
@ -348,11 +349,377 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
}
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> SendRequestAsync(string uri)
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFile_EmptyFileCountUnspecified_SetsChunkedAndFlushesHeaders()
|
||||
{
|
||||
using (HttpClient client = new HttpClient())
|
||||
var emptyFilePath = Path.Combine(Directory.GetCurrentDirectory(), "zz_" + Guid.NewGuid().ToString() + "EmptyTestFile.txt");
|
||||
var emptyFile = File.Create(emptyFilePath, 1024);
|
||||
emptyFile.Dispose();
|
||||
try
|
||||
{
|
||||
return await client.GetAsync(uri);
|
||||
using (Utilities.CreateHttpServer(out var address, async httpContext =>
|
||||
{
|
||||
await httpContext.Response.SendFileAsync(emptyFilePath, 0, null, CancellationToken.None);
|
||||
Assert.True(httpContext.Response.HasStarted);
|
||||
await httpContext.Response.Body.WriteAsync(new byte[10], 0, 10, CancellationToken.None);
|
||||
}))
|
||||
{
|
||||
var response = await SendRequestAsync(address);
|
||||
Assert.Equal(200, (int)response.StatusCode);
|
||||
Assert.False(response.Content.Headers.TryGetValues("content-length", out var contentLength), "Content-Length");
|
||||
Assert.True(response.Headers.TransferEncodingChunked.HasValue);
|
||||
Assert.Equal(10, (await response.Content.ReadAsByteArrayAsync()).Length);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(emptyFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFile_WithActiveCancellationToken_Success()
|
||||
{
|
||||
using (Utilities.CreateHttpServer(out var address, async httpContext =>
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
// First write sends headers
|
||||
await httpContext.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
await httpContext.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
}))
|
||||
{
|
||||
var response = await SendRequestAsync(address);
|
||||
Assert.Equal(200, (int)response.StatusCode);
|
||||
Assert.Equal(FileLength * 2, (await response.Content.ReadAsByteArrayAsync()).Length);
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFile_WithTimerCancellationToken_Success()
|
||||
{
|
||||
using (Utilities.CreateHttpServer(out var address, async httpContext =>
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(TimeSpan.FromSeconds(10));
|
||||
// First write sends headers
|
||||
await httpContext.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
await httpContext.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
}))
|
||||
{
|
||||
var response = await SendRequestAsync(address);
|
||||
Assert.Equal(200, (int)response.StatusCode);
|
||||
Assert.Equal(FileLength * 2, (await response.Content.ReadAsByteArrayAsync()).Length);
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFileWriteExceptions_FirstCallWithCanceledCancellationToken_CancelsAndAborts()
|
||||
{
|
||||
var testComplete = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
using (Utilities.CreateHttpServer(out var address, httpContext =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
// First write sends headers
|
||||
var writeTask = httpContext.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
Assert.True(writeTask.IsCanceled);
|
||||
testComplete.SetResult(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
testComplete.SetException(ex);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}, options => options.ThrowWriteExceptions = true))
|
||||
{
|
||||
await Assert.ThrowsAsync<HttpRequestException>(() => SendRequestAsync(address));
|
||||
await testComplete.Task.WithTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFile_FirstSendWithCanceledCancellationToken_CancelsAndAborts()
|
||||
{
|
||||
var testComplete = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
using (Utilities.CreateHttpServer(out var address, httpContext =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
// First write sends headers
|
||||
var writeTask = httpContext.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
Assert.True(writeTask.IsCanceled);
|
||||
testComplete.SetResult(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
testComplete.SetException(ex);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}))
|
||||
{
|
||||
await Assert.ThrowsAsync<HttpRequestException>(() => SendRequestAsync(address));
|
||||
await testComplete.Task.WithTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFileExceptions_SecondSendWithCanceledCancellationToken_CancelsAndAborts()
|
||||
{
|
||||
var testComplete = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
using (Utilities.CreateHttpServer(out var address, async httpContext =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
// First write sends headers
|
||||
await httpContext.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
cts.Cancel();
|
||||
var writeTask = httpContext.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
Assert.True(writeTask.IsCanceled);
|
||||
testComplete.SetResult(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
testComplete.SetException(ex);
|
||||
}
|
||||
}, options => options.ThrowWriteExceptions = true))
|
||||
{
|
||||
await Assert.ThrowsAsync<HttpRequestException>(() => SendRequestAsync(address));
|
||||
await testComplete.Task.WithTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFile_SecondSendWithCanceledCancellationToken_CancelsAndAborts()
|
||||
{
|
||||
var testComplete = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
using (Utilities.CreateHttpServer(out var address, async httpContext =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
// First write sends headers
|
||||
await httpContext.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
cts.Cancel();
|
||||
var writeTask = httpContext.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
|
||||
Assert.True(writeTask.IsCanceled);
|
||||
testComplete.SetResult(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
testComplete.SetException(ex);
|
||||
}
|
||||
}))
|
||||
{
|
||||
await Assert.ThrowsAsync<HttpRequestException>(() => SendRequestAsync(address));
|
||||
await testComplete.Task.WithTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFileExceptions_ClientDisconnectsBeforeFirstSend_SendThrows()
|
||||
{
|
||||
var requestReceived = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var requestCancelled = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var cancellationReceived = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var testComplete = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
using (Utilities.CreateHttpServer(out var address, async httpContext =>
|
||||
{
|
||||
httpContext.RequestAborted.Register(() => cancellationReceived.SetResult(0));
|
||||
requestReceived.SetResult(0);
|
||||
await requestCancelled.Task;
|
||||
|
||||
try
|
||||
{
|
||||
await Assert.ThrowsAsync<IOException>(async () =>
|
||||
{
|
||||
// It can take several tries before Send notices the disconnect.
|
||||
for (int i = 0; i < Utilities.WriteRetryLimit; i++)
|
||||
{
|
||||
await httpContext.Response.SendFileAsync(AbsoluteFilePath, 0, null);
|
||||
}
|
||||
});
|
||||
|
||||
await Assert.ThrowsAsync<ObjectDisposedException>(() =>
|
||||
httpContext.Response.SendFileAsync(AbsoluteFilePath, 0, null));
|
||||
|
||||
testComplete.SetResult(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
testComplete.SetException(ex);
|
||||
}
|
||||
|
||||
}, options => options.ThrowWriteExceptions = true))
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
var responseTask = SendRequestAsync(address, cts.Token);
|
||||
await requestReceived.Task.WithTimeout();
|
||||
// First write sends headers
|
||||
cts.Cancel();
|
||||
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => responseTask);
|
||||
requestCancelled.SetResult(0);
|
||||
|
||||
await testComplete.Task.WithTimeout();
|
||||
await cancellationReceived.Task.WithTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFile_ClientDisconnectsBeforeFirstSend_SendCompletesSilently()
|
||||
{
|
||||
var requestReceived = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var requestCancelled = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var cancellationReceived = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var testComplete = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
using (Utilities.CreateHttpServer(out var address, async httpContext =>
|
||||
{
|
||||
httpContext.RequestAborted.Register(() => cancellationReceived.SetResult(0));
|
||||
requestReceived.SetResult(0);
|
||||
await requestCancelled.Task;
|
||||
|
||||
try
|
||||
{
|
||||
// It can take several tries before Send notices the disconnect.
|
||||
for (int i = 0; i < Utilities.WriteRetryLimit; i++)
|
||||
{
|
||||
await httpContext.Response.SendFileAsync(AbsoluteFilePath, 0, null);
|
||||
}
|
||||
|
||||
testComplete.SetResult(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
testComplete.SetException(ex);
|
||||
}
|
||||
|
||||
}))
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
var responseTask = SendRequestAsync(address, cts.Token);
|
||||
await requestReceived.Task.WithTimeout();
|
||||
// First write sends headers
|
||||
cts.Cancel();
|
||||
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => responseTask);
|
||||
requestCancelled.SetResult(0);
|
||||
|
||||
await testComplete.Task.WithTimeout();
|
||||
await cancellationReceived.Task.WithTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFileExceptions_ClientDisconnectsBeforeSecondSend_SendThrows()
|
||||
{
|
||||
var firstSendComplete = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var clientDisconnected = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var cancellationReceived = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var testComplete = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
using (Utilities.CreateHttpServer(out var address, async httpContext =>
|
||||
{
|
||||
httpContext.RequestAborted.Register(() => cancellationReceived.SetResult(0));
|
||||
// First write sends headers
|
||||
await httpContext.Response.SendFileAsync(AbsoluteFilePath, 0, null);
|
||||
firstSendComplete.SetResult(0);
|
||||
await clientDisconnected.Task;
|
||||
|
||||
try
|
||||
{
|
||||
await Assert.ThrowsAsync<IOException>(async () =>
|
||||
{
|
||||
// It can take several tries before Write notices the disconnect.
|
||||
for (int i = 0; i < Utilities.WriteRetryLimit; i++)
|
||||
{
|
||||
await httpContext.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
|
||||
}
|
||||
});
|
||||
|
||||
testComplete.SetResult(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
testComplete.SetException(ex);
|
||||
}
|
||||
}, options => options.ThrowWriteExceptions = true))
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
var response = await client.GetAsync(address, HttpCompletionOption.ResponseHeadersRead);
|
||||
response.EnsureSuccessStatusCode();
|
||||
// Drain data from the connection so that SendFileAsync can complete.
|
||||
var bufferTask = response.Content.LoadIntoBufferAsync();
|
||||
|
||||
await firstSendComplete.Task.WithTimeout();
|
||||
|
||||
// Abort
|
||||
response.Dispose();
|
||||
}
|
||||
clientDisconnected.SetResult(0);
|
||||
await testComplete.Task.WithTimeout();
|
||||
await cancellationReceived.Task.WithTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ResponseSendFile_ClientDisconnectsBeforeSecondSend_SendCompletesSilently()
|
||||
{
|
||||
var firstSendComplete = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var clientDisconnected = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var cancellationReceived = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var testComplete = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
using (Utilities.CreateHttpServer(out var address, async httpContext =>
|
||||
{
|
||||
httpContext.RequestAborted.Register(() => cancellationReceived.SetResult(0));
|
||||
// First write sends headers
|
||||
await httpContext.Response.SendFileAsync(AbsoluteFilePath, 0, null);
|
||||
firstSendComplete.SetResult(0);
|
||||
await clientDisconnected.Task;
|
||||
|
||||
try
|
||||
{
|
||||
// It can take several tries before Write notices the disconnect.
|
||||
for (int i = 0; i < Utilities.WriteRetryLimit; i++)
|
||||
{
|
||||
await httpContext.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
|
||||
}
|
||||
|
||||
testComplete.SetResult(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
testComplete.SetException(ex);
|
||||
}
|
||||
}))
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
var response = await client.GetAsync(address, HttpCompletionOption.ResponseHeadersRead);
|
||||
response.EnsureSuccessStatusCode();
|
||||
// Drain data from the connection so that SendFileAsync can complete.
|
||||
var bufferTask = response.Content.LoadIntoBufferAsync();
|
||||
|
||||
await firstSendComplete.Task.WithTimeout();
|
||||
|
||||
// Abort
|
||||
response.Dispose();
|
||||
}
|
||||
clientDisconnected.SetResult(0);
|
||||
await testComplete.Task.WithTimeout();
|
||||
await cancellationReceived.Task.WithTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> SendRequestAsync(string uri, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
using (HttpClient client = new HttpClient() { Timeout = Utilities.DefaultTimeout })
|
||||
{
|
||||
return await client.GetAsync(uri, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
@ -24,6 +25,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
private static int NextPort = BasePort;
|
||||
private static object PortLock = new object();
|
||||
internal static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(15);
|
||||
internal static readonly int WriteRetryLimit = 1000;
|
||||
|
||||
internal static IServer CreateHttpServer(out string baseAddress, RequestDelegate app)
|
||||
{
|
||||
|
@ -31,7 +33,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
return CreateDynamicHttpServer(string.Empty, out root, out baseAddress, options => { }, app);
|
||||
}
|
||||
|
||||
internal static IServer CreateHttpServer(out string baseAddress, Action<HttpSysOptions> configureOptions, RequestDelegate app)
|
||||
internal static IServer CreateHttpServer(out string baseAddress, RequestDelegate app, Action<HttpSysOptions> configureOptions)
|
||||
{
|
||||
string root;
|
||||
return CreateDynamicHttpServer(string.Empty, out root, out baseAddress, configureOptions, app);
|
||||
|
@ -148,5 +150,38 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
server.StartAsync(new DummyApplication(app), CancellationToken.None).Wait();
|
||||
return server;
|
||||
}
|
||||
|
||||
internal static Task WithTimeout(this Task task) => task.WithTimeout(DefaultTimeout);
|
||||
|
||||
internal static async Task WithTimeout(this Task task, TimeSpan timeout)
|
||||
{
|
||||
var completedTask = await Task.WhenAny(task, Task.Delay(timeout));
|
||||
|
||||
if (completedTask == task)
|
||||
{
|
||||
await task;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new TimeoutException("The task has timed out.");
|
||||
}
|
||||
}
|
||||
|
||||
internal static Task<T> WithTimeout<T>(this Task<T> task) => task.WithTimeout(DefaultTimeout);
|
||||
|
||||
internal static async Task<T> WithTimeout<T>(this Task<T> task, TimeSpan timeout)
|
||||
{
|
||||
var completedTask = await Task.WhenAny(task, Task.Delay(timeout));
|
||||
|
||||
if (completedTask == task)
|
||||
{
|
||||
return await task;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new TimeoutException("The task has timed out.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче