Always flush headers on first response write (#1202).
This commit is contained in:
parent
0fbbd9e165
commit
757952d4d3
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue