diff --git a/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/Listener/ResponseSendFileTests.cs b/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/Listener/ResponseSendFileTests.cs deleted file mode 100644 index 0417776e96..0000000000 --- a/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/Listener/ResponseSendFileTests.cs +++ /dev/null @@ -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 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(() => 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(() => 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(() => 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(() => 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(() => responseTask); - - Assert.True(context.DisconnectToken.WaitHandle.WaitOne(TimeSpan.FromSeconds(5))); - await Assert.ThrowsAsync(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(() => - 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(() => 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(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 SendRequestAsync(string uri, CancellationToken cancellationToken = new CancellationToken()) - { - using (HttpClient client = new HttpClient()) - { - return await client.GetAsync(uri, cancellationToken); - } - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/OpaqueUpgradeTests.cs b/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/OpaqueUpgradeTests.cs index 609bc59fdd..c9bebee2de 100644 --- a/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/OpaqueUpgradeTests.cs +++ b/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/OpaqueUpgradeTests.cs @@ -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(); 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)) { diff --git a/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/RequestBodyLimitTests.cs b/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/RequestBodyLimitTests.cs index ae203ceab1..bfeb4f42b9 100644 --- a/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/RequestBodyLimitTests.cs +++ b/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/RequestBodyLimitTests.cs @@ -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().AllowSynchronousIO = true; var feature = httpContext.Features.Get(); @@ -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(); 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(); 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().AllowSynchronousIO = true; var feature = httpContext.Features.Get(); @@ -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(); 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(); 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().AllowSynchronousIO = true; var feature = httpContext.Features.Get(); @@ -164,7 +164,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys ex = Assert.Throws(() => 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(); Assert.NotNull(feature); @@ -187,7 +187,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys ex = Assert.Throws(() => { 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(); Assert.NotNull(feature); @@ -210,7 +210,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys ex = Assert.Throws(() => 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().AllowSynchronousIO = true; var feature = httpContext.Features.Get(); @@ -234,7 +234,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys ex = Assert.Throws(() => 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(); 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(() => 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(); Assert.NotNull(feature); @@ -280,7 +280,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys ex = Assert.Throws(() => 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().AllowSynchronousIO = true; var feature = httpContext.Features.Get(); @@ -306,7 +306,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys var ex = Assert.Throws(() => 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(); Assert.NotNull(feature); @@ -330,7 +330,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys content.Block.Release(); var ex = await Assert.ThrowsAsync(() => 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(); 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(); 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); diff --git a/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/ResponseSendFileTests.cs b/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/ResponseSendFileTests.cs index d3a8c5e5d0..69f0a64ec9 100644 --- a/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/ResponseSendFileTests.cs +++ b/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/ResponseSendFileTests.cs @@ -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 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(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(() => SendRequestAsync(address)); + await testComplete.Task.WithTimeout(); + } + } + + [ConditionalFact] + public async Task ResponseSendFile_FirstSendWithCanceledCancellationToken_CancelsAndAborts() + { + var testComplete = new TaskCompletionSource(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(() => SendRequestAsync(address)); + await testComplete.Task.WithTimeout(); + } + } + + [ConditionalFact] + public async Task ResponseSendFileExceptions_SecondSendWithCanceledCancellationToken_CancelsAndAborts() + { + var testComplete = new TaskCompletionSource(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(() => SendRequestAsync(address)); + await testComplete.Task.WithTimeout(); + } + } + + [ConditionalFact] + public async Task ResponseSendFile_SecondSendWithCanceledCancellationToken_CancelsAndAborts() + { + var testComplete = new TaskCompletionSource(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(() => SendRequestAsync(address)); + await testComplete.Task.WithTimeout(); + } + } + + [ConditionalFact] + public async Task ResponseSendFileExceptions_ClientDisconnectsBeforeFirstSend_SendThrows() + { + var requestReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var requestCancelled = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var cancellationReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var testComplete = new TaskCompletionSource(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(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(() => + 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(() => responseTask); + requestCancelled.SetResult(0); + + await testComplete.Task.WithTimeout(); + await cancellationReceived.Task.WithTimeout(); + } + } + + [ConditionalFact] + public async Task ResponseSendFile_ClientDisconnectsBeforeFirstSend_SendCompletesSilently() + { + var requestReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var requestCancelled = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var cancellationReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var testComplete = new TaskCompletionSource(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(() => responseTask); + requestCancelled.SetResult(0); + + await testComplete.Task.WithTimeout(); + await cancellationReceived.Task.WithTimeout(); + } + } + + [ConditionalFact] + public async Task ResponseSendFileExceptions_ClientDisconnectsBeforeSecondSend_SendThrows() + { + var firstSendComplete = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var clientDisconnected = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var cancellationReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var testComplete = new TaskCompletionSource(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(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(TaskCreationOptions.RunContinuationsAsynchronously); + var clientDisconnected = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var cancellationReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var testComplete = new TaskCompletionSource(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 SendRequestAsync(string uri, CancellationToken cancellationToken = new CancellationToken()) + { + using (HttpClient client = new HttpClient() { Timeout = Utilities.DefaultTimeout }) + { + return await client.GetAsync(uri, cancellationToken); } } } diff --git a/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/Utilities.cs b/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/Utilities.cs index e46df7666f..5c340a93c9 100644 --- a/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/Utilities.cs +++ b/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/Utilities.cs @@ -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 configureOptions, RequestDelegate app) + internal static IServer CreateHttpServer(out string baseAddress, RequestDelegate app, Action 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 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) + { + return await task; + } + else + { + throw new TimeoutException("The task has timed out."); + } + } } }