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 System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Filter.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) public void Write(ArraySegment<byte> buffer, bool chunk)
{ {
if (buffer.Count == 0 )
{
return;
}
if (chunk && buffer.Array != null) if (chunk && buffer.Array != null)
{ {
var beginChunkBytes = ChunkWriter.BeginChunkBytes(buffer.Count); var beginChunkBytes = ChunkWriter.BeginChunkBytes(buffer.Count);
@ -117,5 +113,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Filter.Internal
end.Block.Pool.Return(end.Block); 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> _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> _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[] _bytesConnectionClose = Encoding.ASCII.GetBytes("\r\nConnection: close");
private static readonly byte[] _bytesConnectionKeepAlive = Encoding.ASCII.GetBytes("\r\nConnection: keep-alive"); 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() public void Flush()
{ {
ProduceStartAndFireOnStarting().GetAwaiter().GetResult(); ProduceStartAndFireOnStarting().GetAwaiter().GetResult();
SocketOutput.Write(_emptyData); SocketOutput.Flush();
} }
public async Task FlushAsync(CancellationToken cancellationToken) public async Task FlushAsync(CancellationToken cancellationToken)
{ {
await ProduceStartAndFireOnStarting(); await ProduceStartAndFireOnStarting();
await SocketOutput.WriteAsync(_emptyData, cancellationToken: cancellationToken); await SocketOutput.FlushAsync(cancellationToken);
} }
public void Write(ArraySegment<byte> data) public void Write(ArraySegment<byte> data)
@ -768,7 +767,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
ProduceStart(appCompleted: true); ProduceStart(appCompleted: true);
// Force flush // Force flush
await SocketOutput.WriteAsync(_emptyData); await SocketOutput.FlushAsync();
await WriteSuffix(); await WriteSuffix();
} }

View File

@ -15,6 +15,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
void Write(ArraySegment<byte> buffer, bool chunk = false); void Write(ArraySegment<byte> buffer, bool chunk = false);
Task WriteAsync(ArraySegment<byte> buffer, bool chunk = false, CancellationToken cancellationToken = default(CancellationToken)); Task WriteAsync(ArraySegment<byte> buffer, bool chunk = false, CancellationToken cancellationToken = default(CancellationToken));
void Flush();
Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken));
/// <summary> /// <summary>
/// Returns an iterator pointing to the tail of the response buffer. Response data can be appended /// 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 // 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 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 WaitCallback _returnBlocks = (state) => ReturnBlocks((MemoryPoolBlock)state);
private static readonly Action<object> _connectionCancellation = (state) => ((SocketOutput)state).CancellationTriggered(); 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); 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) private static void BytesBetween(MemoryPoolIterator start, MemoryPoolIterator end, out int bytes, out int buffers)
{ {
if (start.Block == end.Block) if (start.Block == end.Block)

View File

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