Rewrite Listener.ResponseSendFileTests

This commit is contained in:
Chris Ross (ASP.NET) 2018-10-29 12:19:36 -07:00
Родитель 7974467dc9
Коммит 2451b68ca0
5 изменённых файлов: 440 добавлений и 382 удалений

Просмотреть файл

@ -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.");
}
}
}
}