Always flush headers on first response write (#1202).

This commit is contained in:
Stephen Halter 2016-11-07 15:55:46 -08:00
parent 0fbbd9e165
commit 757952d4d3
3 changed files with 134 additions and 6 deletions

View File

@ -520,6 +520,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
public void Write(ArraySegment<byte> data)
{
// For the first write, ensure headers are flushed if Write(Chunked)isn't called.
var firstWrite = !HasResponseStarted;
VerifyAndUpdateWrite(data.Count);
ProduceStartAndFireOnStarting().GetAwaiter().GetResult();
@ -529,6 +532,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
if (data.Count == 0)
{
if (firstWrite)
{
Flush();
}
return;
}
WriteChunked(data);
@ -541,6 +548,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
else
{
HandleNonBodyResponseWrite();
if (firstWrite)
{
Flush();
}
}
}
@ -581,14 +593,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
await ProduceStartAndFireOnStarting();
// WriteAsyncAwaited is only called for the first write to the body.
// Ensure headers are flushed if Write(Chunked)Async isn't called.
if (_canHaveBody)
{
if (_autoChunk)
{
if (data.Count == 0)
{
await FlushAsync(cancellationToken);
return;
}
await WriteChunkedAsync(data, cancellationToken);
}
else
@ -599,7 +615,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
else
{
HandleNonBodyResponseWrite();
return;
await FlushAsync(cancellationToken);
}
}

View File

@ -7,6 +7,7 @@ using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@ -767,11 +768,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
using (var connection = server.CreateConnection())
{
await connection.SendEnd(
await connection.Send(
"HEAD / HTTP/1.1",
"",
"");
await connection.ReceiveEnd(
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 42",
@ -782,26 +783,95 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
[Fact]
public async Task HeadResponseCanContainContentLengthHeaderButBodyNotWritten()
public async Task HeadResponseBodyNotWrittenWithAsyncWrite()
{
var flushed = new SemaphoreSlim(0, 1);
using (var server = new TestServer(async httpContext =>
{
httpContext.Response.ContentLength = 12;
await httpContext.Response.WriteAsync("hello, world");
await flushed.WaitAsync();
}, new TestServiceContext()))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"HEAD / HTTP/1.1",
"",
"");
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 12",
"",
"");
flushed.Release();
}
}
}
[Fact]
public async Task HeadResponseBodyNotWrittenWithSyncWrite()
{
var flushed = new SemaphoreSlim(0, 1);
using (var server = new TestServer(httpContext =>
{
httpContext.Response.ContentLength = 12;
httpContext.Response.Body.Write(Encoding.ASCII.GetBytes("hello, world"), 0, 12);
flushed.Wait();
return TaskCache.CompletedTask;
}, new TestServiceContext()))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"HEAD / HTTP/1.1",
"",
"");
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 12",
"",
"");
flushed.Release();
}
}
}
[Fact]
public async Task ZeroLengthWritesFlushHeaders()
{
var flushed = new SemaphoreSlim(0, 1);
using (var server = new TestServer(async httpContext =>
{
httpContext.Response.ContentLength = 12;
await httpContext.Response.WriteAsync("");
flushed.Wait();
await httpContext.Response.WriteAsync("hello, world");
}, new TestServiceContext()))
{
using (var connection = server.CreateConnection())
{
await connection.SendEnd(
"HEAD / HTTP/1.1",
"GET / HTTP/1.1",
"",
"");
await connection.ReceiveEnd(
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 12",
"",
"");
flushed.Release();
await connection.ReceiveEnd("hello, world");
}
}
}

View File

@ -192,6 +192,48 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
}
}
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task ZeroLengthWritesFlushHeaders(TestServiceContext testContext)
{
var flushed = new SemaphoreSlim(0, 1);
using (var server = new TestServer(async httpContext =>
{
var response = httpContext.Response;
await response.WriteAsync("");
await flushed.WaitAsync();
await response.WriteAsync("Hello World!");
}, testContext))
{
using (var connection = server.CreateConnection())
{
await connection.SendEnd(
"GET / HTTP/1.1",
"",
"");
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {testContext.DateHeaderValue}",
"Transfer-Encoding: chunked",
"",
"");
flushed.Release();
await connection.ReceiveEnd(
"c",
"Hello World!",
"0",
"",
"");
}
}
}
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task EmptyResponseBodyHandledCorrectlyWithZeroLengthWrite(TestServiceContext testContext)