diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs index bb236f6846..43bed07978 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs @@ -257,8 +257,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http if (_autoChunk) { - WriteChunkPrefix(numOctets: 0); - WriteChunkSuffix(); + WriteChunkedResponseSuffix(); } } catch (Exception ex) @@ -278,6 +277,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Http if (_autoChunk) { + if (data.Count == 0) + { + callback(null, state); + return; + } + WriteChunkPrefix(data.Count); } @@ -306,6 +311,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http } private static readonly ArraySegment _endChunkBytes = CreateAsciiByteArraySegment("\r\n"); + private static readonly ArraySegment _endChunkedResponseBytes = CreateAsciiByteArraySegment("0\r\n\r\n"); private void WriteChunkSuffix() { @@ -321,6 +327,20 @@ namespace Microsoft.AspNet.Server.Kestrel.Http immediate: true); } + private void WriteChunkedResponseSuffix() + { + SocketOutput.Write(_endChunkedResponseBytes, + (error, _) => + { + if (error != null) + { + Trace.WriteLine("WriteChunkedResponseSuffix" + error.ToString()); + } + }, + null, + immediate: true); + } + public void Upgrade(IDictionary options, Func callback) { _keepAlive = false; diff --git a/test/Microsoft.AspNet.Server.KestrelTests/ChunkedResponseTests.cs b/test/Microsoft.AspNet.Server.KestrelTests/ChunkedResponseTests.cs new file mode 100644 index 0000000000..a1b7ac186d --- /dev/null +++ b/test/Microsoft.AspNet.Server.KestrelTests/ChunkedResponseTests.cs @@ -0,0 +1,75 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNet.Server.KestrelTests +{ + public class ChunkedResponseTests + { + [Fact] + public async Task ResponsesAreChunkedAutomatically() + { + using (var server = new TestServer(async frame => + { + frame.ResponseHeaders.Clear(); + await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("Hello "), 0, 6); + await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("World!"), 0, 6); + })) + { + using (var connection = new TestConnection()) + { + await connection.SendEnd( + "GET / HTTP/1.1", + "", + ""); + await connection.ReceiveEnd( + "HTTP/1.1 200 OK", + "Transfer-Encoding: chunked", + "", + "6", + "Hello ", + "6", + "World!", + "0", + "", + ""); + } + } + } + + [Fact] + public async Task ZeroLengthWritesAreIgnored() + { + using (var server = new TestServer(async frame => + { + frame.ResponseHeaders.Clear(); + await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("Hello "), 0, 6); + await frame.ResponseBody.WriteAsync(new byte[0], 0, 0); + await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("World!"), 0, 6); + })) + { + using (var connection = new TestConnection()) + { + await connection.SendEnd( + "GET / HTTP/1.1", + "", + ""); + await connection.ReceiveEnd( + "HTTP/1.1 200 OK", + "Transfer-Encoding: chunked", + "", + "6", + "Hello ", + "6", + "World!", + "0", + "", + ""); + } + } + } + } +}