Avoid zero-length writes to flush connection filter stream

- This works around a zero-length write bug in SslStream.
- We already assume connection filter streams auto flush.

#1195
This commit is contained in:
Stephen Halter 2016-11-04 15:09:51 -07:00
parent f5d6f39fa0
commit b3aca0413c
5 changed files with 36 additions and 9 deletions

View File

@ -8,6 +8,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Filter.Internal
{
@ -34,11 +35,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Filter.Internal
public void Write(ArraySegment<byte> buffer, bool chunk)
{
if (buffer.Count == 0 )
{
return;
}
if (chunk && buffer.Array != null)
{
var beginChunkBytes = ChunkWriter.BeginChunkBytes(buffer.Count);
@ -117,5 +113,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Filter.Internal
end.Block.Pool.Return(end.Block);
}
// Flush no-ops. We rely on connection filter streams to auto-flush.
public void Flush()
{
}
public Task FlushAsync(CancellationToken cancellationToken)
{
return TaskCache.CompletedTask;
}
}
}

View File

@ -26,7 +26,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
private static readonly ArraySegment<byte> _endChunkedResponseBytes = CreateAsciiByteArraySegment("0\r\n\r\n");
private static readonly ArraySegment<byte> _continueBytes = CreateAsciiByteArraySegment("HTTP/1.1 100 Continue\r\n\r\n");
private static readonly ArraySegment<byte> _emptyData = new ArraySegment<byte>(new byte[0]);
private static readonly byte[] _bytesConnectionClose = Encoding.ASCII.GetBytes("\r\nConnection: close");
private static readonly byte[] _bytesConnectionKeepAlive = Encoding.ASCII.GetBytes("\r\nConnection: keep-alive");
@ -506,13 +505,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
public void Flush()
{
ProduceStartAndFireOnStarting().GetAwaiter().GetResult();
SocketOutput.Write(_emptyData);
SocketOutput.Flush();
}
public async Task FlushAsync(CancellationToken cancellationToken)
{
await ProduceStartAndFireOnStarting();
await SocketOutput.WriteAsync(_emptyData, cancellationToken: cancellationToken);
await SocketOutput.FlushAsync(cancellationToken);
}
public void Write(ArraySegment<byte> data)
@ -768,7 +767,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
ProduceStart(appCompleted: true);
// Force flush
await SocketOutput.WriteAsync(_emptyData);
await SocketOutput.FlushAsync();
await WriteSuffix();
}

View File

@ -15,6 +15,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
void Write(ArraySegment<byte> buffer, bool chunk = false);
Task WriteAsync(ArraySegment<byte> buffer, bool chunk = false, CancellationToken cancellationToken = default(CancellationToken));
void Flush();
Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Returns an iterator pointing to the tail of the response buffer. Response data can be appended

View File

@ -21,6 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
// Well behaved WriteAsync users should await returned task, so there is no need to allocate more per connection by default
private const int _initialTaskQueues = 1;
private static readonly ArraySegment<byte> _emptyData = new ArraySegment<byte>(new byte[0]);
private static readonly WaitCallback _returnBlocks = (state) => ReturnBlocks((MemoryPoolBlock)state);
private static readonly Action<object> _connectionCancellation = (state) => ((SocketOutput)state).CancellationTriggered();
@ -534,6 +535,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
return WriteAsync(buffer, cancellationToken, chunk);
}
void ISocketOutput.Flush()
{
WriteAsync(_emptyData, default(CancellationToken), isSync: true).GetAwaiter().GetResult();
}
Task ISocketOutput.FlushAsync(CancellationToken cancellationToken)
{
return WriteAsync(_emptyData, cancellationToken);
}
private static void BytesBetween(MemoryPoolIterator start, MemoryPoolIterator end, out int bytes, out int buffers)
{
if (start.Block == end.Block)

View File

@ -29,5 +29,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
{
return TaskCache.CompletedTask;
}
public void Flush()
{
}
public Task FlushAsync(CancellationToken cancellationToken = new CancellationToken())
{
return TaskCache.CompletedTask;
}
}
}