Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Commit b3aca04

Browse files
committed
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
1 parent f5d6f39 commit b3aca04

File tree

5 files changed

+36
-9
lines changed

5 files changed

+36
-9
lines changed

src/Microsoft.AspNetCore.Server.Kestrel/Filter/Internal/StreamSocketOutput.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Threading.Tasks;
99
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
1010
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
11+
using Microsoft.Extensions.Internal;
1112

1213
namespace Microsoft.AspNetCore.Server.Kestrel.Filter.Internal
1314
{
@@ -34,11 +35,6 @@ public StreamSocketOutput(string connectionId, Stream outputStream, MemoryPool m
3435

3536
public void Write(ArraySegment<byte> buffer, bool chunk)
3637
{
37-
if (buffer.Count == 0 )
38-
{
39-
return;
40-
}
41-
4238
if (chunk && buffer.Array != null)
4339
{
4440
var beginChunkBytes = ChunkWriter.BeginChunkBytes(buffer.Count);
@@ -117,5 +113,15 @@ public void ProducingComplete(MemoryPoolIterator end)
117113

118114
end.Block.Pool.Return(end.Block);
119115
}
116+
117+
// Flush no-ops. We rely on connection filter streams to auto-flush.
118+
public void Flush()
119+
{
120+
}
121+
122+
public Task FlushAsync(CancellationToken cancellationToken)
123+
{
124+
return TaskCache.CompletedTask;
125+
}
120126
}
121127
}

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ public abstract partial class Frame : IFrameControl
2626
{
2727
private static readonly ArraySegment<byte> _endChunkedResponseBytes = CreateAsciiByteArraySegment("0\r\n\r\n");
2828
private static readonly ArraySegment<byte> _continueBytes = CreateAsciiByteArraySegment("HTTP/1.1 100 Continue\r\n\r\n");
29-
private static readonly ArraySegment<byte> _emptyData = new ArraySegment<byte>(new byte[0]);
3029

3130
private static readonly byte[] _bytesConnectionClose = Encoding.ASCII.GetBytes("\r\nConnection: close");
3231
private static readonly byte[] _bytesConnectionKeepAlive = Encoding.ASCII.GetBytes("\r\nConnection: keep-alive");
@@ -506,13 +505,13 @@ protected async Task FireOnCompleted()
506505
public void Flush()
507506
{
508507
ProduceStartAndFireOnStarting().GetAwaiter().GetResult();
509-
SocketOutput.Write(_emptyData);
508+
SocketOutput.Flush();
510509
}
511510

512511
public async Task FlushAsync(CancellationToken cancellationToken)
513512
{
514513
await ProduceStartAndFireOnStarting();
515-
await SocketOutput.WriteAsync(_emptyData, cancellationToken: cancellationToken);
514+
await SocketOutput.FlushAsync(cancellationToken);
516515
}
517516

518517
public void Write(ArraySegment<byte> data)
@@ -768,7 +767,7 @@ private async Task ProduceEndAwaited()
768767
ProduceStart(appCompleted: true);
769768

770769
// Force flush
771-
await SocketOutput.WriteAsync(_emptyData);
770+
await SocketOutput.FlushAsync();
772771

773772
await WriteSuffix();
774773
}

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/ISocketOutput.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public interface ISocketOutput
1515
{
1616
void Write(ArraySegment<byte> buffer, bool chunk = false);
1717
Task WriteAsync(ArraySegment<byte> buffer, bool chunk = false, CancellationToken cancellationToken = default(CancellationToken));
18+
void Flush();
19+
Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken));
1820

1921
/// <summary>
2022
/// Returns an iterator pointing to the tail of the response buffer. Response data can be appended

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/SocketOutput.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class SocketOutput : ISocketOutput
2121
// Well behaved WriteAsync users should await returned task, so there is no need to allocate more per connection by default
2222
private const int _initialTaskQueues = 1;
2323

24+
private static readonly ArraySegment<byte> _emptyData = new ArraySegment<byte>(new byte[0]);
2425
private static readonly WaitCallback _returnBlocks = (state) => ReturnBlocks((MemoryPoolBlock)state);
2526
private static readonly Action<object> _connectionCancellation = (state) => ((SocketOutput)state).CancellationTriggered();
2627

@@ -534,6 +535,16 @@ Task ISocketOutput.WriteAsync(ArraySegment<byte> buffer, bool chunk, Cancellatio
534535
return WriteAsync(buffer, cancellationToken, chunk);
535536
}
536537

538+
void ISocketOutput.Flush()
539+
{
540+
WriteAsync(_emptyData, default(CancellationToken), isSync: true).GetAwaiter().GetResult();
541+
}
542+
543+
Task ISocketOutput.FlushAsync(CancellationToken cancellationToken)
544+
{
545+
return WriteAsync(_emptyData, cancellationToken);
546+
}
547+
537548
private static void BytesBetween(MemoryPoolIterator start, MemoryPoolIterator end, out int bytes, out int buffers)
538549
{
539550
if (start.Block == end.Block)

test/Microsoft.AspNetCore.Server.KestrelTests/TestHelpers/MockSocketOuptut.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,14 @@ public void Write(ArraySegment<byte> buffer, bool chunk = false)
2929
{
3030
return TaskCache.CompletedTask;
3131
}
32+
33+
public void Flush()
34+
{
35+
}
36+
37+
public Task FlushAsync(CancellationToken cancellationToken = new CancellationToken())
38+
{
39+
return TaskCache.CompletedTask;
40+
}
3241
}
3342
}

0 commit comments

Comments
 (0)