Assume zero length on non-keepalive requests without Content-Length or Transfer-Encoding (#1104).

This commit is contained in:
Cesar Blum Silveira 2016-09-27 16:42:13 -07:00
parent 0312da7df3
commit 73656f6503
6 changed files with 129 additions and 29 deletions

View File

@ -234,6 +234,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var connection = headers.HeaderConnection.ToString(); var connection = headers.HeaderConnection.ToString();
if (connection.Length > 0) if (connection.Length > 0)
{ {
if (connection.Equals("upgrade", StringComparison.OrdinalIgnoreCase))
{
return new ForRemainingData(context);
}
keepAlive = connection.Equals("keep-alive", StringComparison.OrdinalIgnoreCase); keepAlive = connection.Equals("keep-alive", StringComparison.OrdinalIgnoreCase);
} }
@ -257,12 +262,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
} }
if (keepAlive) return new ForContentLength(keepAlive, 0, context);
{
return new ForContentLength(true, 0, context);
}
return new ForRemainingData(context);
} }
private class ForRemainingData : MessageBody private class ForRemainingData : MessageBody

View File

@ -53,15 +53,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
// Disables all code related to computing and limiting the size of the input buffer. // Disables all code related to computing and limiting the size of the input buffer.
Tuple.Create((long?)null, false) Tuple.Create((long?)null, false)
}; };
var sendContentLengthHeaderValues = new[] { true, false };
var sslValues = new[] { true, false }; var sslValues = new[] { true, false };
return from maxRequestBufferSize in maxRequestBufferSizeValues return from maxRequestBufferSize in maxRequestBufferSizeValues
from sendContentLengthHeader in sendContentLengthHeaderValues
from ssl in sslValues from ssl in sslValues
select new object[] { select new object[] {
maxRequestBufferSize.Item1, maxRequestBufferSize.Item1,
sendContentLengthHeader,
ssl, ssl,
maxRequestBufferSize.Item2 maxRequestBufferSize.Item2
}; };
@ -70,7 +67,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[Theory] [Theory]
[MemberData("LargeUploadData")] [MemberData("LargeUploadData")]
public async Task LargeUpload(long? maxRequestBufferSize, bool sendContentLengthHeader, bool ssl, bool expectPause) public async Task LargeUpload(long? maxRequestBufferSize, bool ssl, bool expectPause)
{ {
// Parameters // Parameters
var data = new byte[_dataLength]; var data = new byte[_dataLength];
@ -91,7 +88,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var socket = CreateSocket(port)) using (var socket = CreateSocket(port))
using (var stream = await CreateStreamAsync(socket, ssl, host.GetHost())) using (var stream = await CreateStreamAsync(socket, ssl, host.GetHost()))
{ {
await WritePostRequestHeaders(stream, sendContentLengthHeader ? (int?)data.Length : null); await WritePostRequestHeaders(stream, data.Length);
var bytesWritten = 0; var bytesWritten = 0;
@ -236,15 +233,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
return socket; return socket;
} }
private static async Task WritePostRequestHeaders(Stream stream, int? contentLength) private static async Task WritePostRequestHeaders(Stream stream, int contentLength)
{ {
using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize: 1024, leaveOpen: true)) using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize: 1024, leaveOpen: true))
{ {
await writer.WriteAsync("POST / HTTP/1.0\r\n"); await writer.WriteAsync("POST / HTTP/1.0\r\n");
if (contentLength.HasValue) await writer.WriteAsync($"Content-Length: {contentLength}\r\n");
{
await writer.WriteAsync($"Content-Length: {contentLength.Value}\r\n");
}
await writer.WriteAsync("\r\n"); await writer.WriteAsync("\r\n");
} }
} }

View File

@ -102,6 +102,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
"0", "0",
"", "",
"POST / HTTP/1.0", "POST / HTTP/1.0",
"Content-Length: 7",
"", "",
"Goodbye"); "Goodbye");
await connection.Receive( await connection.Receive(

View File

@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var filter = new RewritingConnectionFilter(); var filter = new RewritingConnectionFilter();
var serviceContext = new TestServiceContext(filter); var serviceContext = new TestServiceContext(filter);
var sendString = "POST / HTTP/1.0\r\n\r\nHello World?"; var sendString = "POST / HTTP/1.0\r\nContent-Length: 12\r\n\r\nHello World?";
using (var server = new TestServer(App, serviceContext)) using (var server = new TestServer(App, serviceContext))
{ {
@ -66,6 +66,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{ {
await connection.SendEnd( await connection.SendEnd(
"POST / HTTP/1.0", "POST / HTTP/1.0",
"Content-Length: 12",
"", "",
"Hello World?"); "Hello World?");
await connection.ReceiveEnd( await connection.ReceiveEnd(
@ -91,6 +92,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{ {
await connection.SendEnd( await connection.SendEnd(
"POST / HTTP/1.0", "POST / HTTP/1.0",
"Content-Length: 12",
"", "",
"Hello World?"); "Hello World?");
} }

View File

@ -14,7 +14,6 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel; using Microsoft.AspNetCore.Server.Kestrel;
using Microsoft.AspNetCore.Server.Kestrel.Internal; using Microsoft.AspNetCore.Server.Kestrel.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
using Xunit; using Xunit;
@ -108,7 +107,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var started = engine.CreateServer(address); var started = engine.CreateServer(address);
var socket = TestConnection.CreateConnectedLoopbackSocket(address.Port); var socket = TestConnection.CreateConnectedLoopbackSocket(address.Port);
socket.Send(Encoding.ASCII.GetBytes("POST / HTTP/1.0\r\n\r\nHello World")); socket.Send(Encoding.ASCII.GetBytes("POST / HTTP/1.0\r\nContent-Length: 11\r\n\r\nHello World"));
socket.Shutdown(SocketShutdown.Send); socket.Shutdown(SocketShutdown.Send);
var buffer = new byte[8192]; var buffer = new byte[8192];
while (true) while (true)
@ -131,6 +130,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{ {
await connection.SendEnd( await connection.SendEnd(
"POST / HTTP/1.0", "POST / HTTP/1.0",
"Content-Length: 11",
"", "",
"Hello World"); "Hello World");
await connection.ReceiveEnd( await connection.ReceiveEnd(
@ -156,6 +156,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
"", "",
"GET / HTTP/1.1", "GET / HTTP/1.1",
"Connection: close", "Connection: close",
"Content-Length: 7",
"", "",
"Goodbye"); "Goodbye");
await connection.ReceiveEnd( await connection.ReceiveEnd(
@ -219,7 +220,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{ {
var requestData = var requestData =
Enumerable.Repeat("GET / HTTP/1.1\r\n", loopCount) Enumerable.Repeat("GET / HTTP/1.1\r\n", loopCount)
.Concat(new[] { "GET / HTTP/1.1\r\nConnection: close\r\n\r\nGoodbye" }); .Concat(new[] { "GET / HTTP/1.1\r\nContent-Length: 7\r\nConnection: close\r\n\r\nGoodbye" });
var response = string.Join("\r\n", new string[] { var response = string.Join("\r\n", new string[] {
"HTTP/1.1 200 OK", "HTTP/1.1 200 OK",
@ -288,6 +289,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
"Connection: keep-alive", "Connection: keep-alive",
"", "",
"POST / HTTP/1.0", "POST / HTTP/1.0",
"Content-Length: 7",
"", "",
"Goodbye"); "Goodbye");
await connection.Receive( await connection.Receive(
@ -320,8 +322,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
"Connection: keep-alive", "Connection: keep-alive",
"", "",
"POST / HTTP/1.0", "POST / HTTP/1.0",
"Content-Length: 7",
"Connection: keep-alive", "Connection: keep-alive",
"Content-Length: 7",
"", "",
"Goodbye"); "Goodbye");
await connection.Receive( await connection.Receive(
@ -354,6 +356,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
"Connection: keep-alive", "Connection: keep-alive",
"", "",
"Hello WorldPOST / HTTP/1.0", "Hello WorldPOST / HTTP/1.0",
"Content-Length: 7",
"", "",
"Goodbye"); "Goodbye");
await connection.Receive( await connection.Receive(
@ -461,11 +464,17 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[MemberData(nameof(ConnectionFilterData))] [MemberData(nameof(ConnectionFilterData))]
public async Task ZeroContentLengthSetAutomaticallyForNonKeepAliveRequests(TestServiceContext testContext) public async Task ZeroContentLengthSetAutomaticallyForNonKeepAliveRequests(TestServiceContext testContext)
{ {
using (var server = new TestServer(EmptyApp, testContext)) using (var server = new TestServer(async httpContext =>
{
Assert.Equal(0, await httpContext.Request.Body.ReadAsync(new byte[1], 0, 1).TimeoutAfter(TimeSpan.FromSeconds(10)));
}, testContext))
{ {
using (var connection = server.CreateConnection()) using (var connection = server.CreateConnection())
{ {
await connection.SendEnd( // Use Send instead of SendEnd to ensure the connection will remain open while
// the app runs and reads 0 bytes from the body nonetheless. This checks that
// https://github.com/aspnet/KestrelHttpServer/issues/1104 is not regressing.
await connection.Send(
"GET / HTTP/1.1", "GET / HTTP/1.1",
"Connection: close", "Connection: close",
"", "",
@ -481,7 +490,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
using (var connection = server.CreateConnection()) using (var connection = server.CreateConnection())
{ {
await connection.SendEnd( await connection.Send(
"GET / HTTP/1.0", "GET / HTTP/1.0",
"", "",
""); "");

View File

@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{ {
using (var input = new TestInput()) using (var input = new TestInput())
{ {
var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders(), input.FrameContext); var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderContentLength = "5" }, input.FrameContext);
var stream = new FrameRequestStream(); var stream = new FrameRequestStream();
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{ {
using (var input = new TestInput()) using (var input = new TestInput())
{ {
var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders(), input.FrameContext); var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderContentLength = "5" }, input.FrameContext);
var stream = new FrameRequestStream(); var stream = new FrameRequestStream();
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
} }
[Fact] [Fact]
public async Task CanHandleLargeBlocks() public void Http10NoContentLength()
{ {
using (var input = new TestInput()) using (var input = new TestInput())
{ {
@ -73,6 +73,102 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var stream = new FrameRequestStream(); var stream = new FrameRequestStream();
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
input.Add("Hello", true);
var buffer1 = new byte[1024];
Assert.Equal(0, stream.Read(buffer1, 0, 1024));
}
}
[Fact]
public async Task Http10NoContentLengthAsync()
{
using (var input = new TestInput())
{
var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders(), input.FrameContext);
var stream = new FrameRequestStream();
stream.StartAcceptingReads(body);
input.Add("Hello", true);
var buffer1 = new byte[1024];
Assert.Equal(0, await stream.ReadAsync(buffer1, 0, 1024));
}
}
[Fact]
public void Http11NoContentLength()
{
using (var input = new TestInput())
{
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders(), input.FrameContext);
var stream = new FrameRequestStream();
stream.StartAcceptingReads(body);
input.Add("Hello", true);
var buffer1 = new byte[1024];
Assert.Equal(0, stream.Read(buffer1, 0, 1024));
}
}
[Fact]
public async Task Http11NoContentLengthAsync()
{
using (var input = new TestInput())
{
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders(), input.FrameContext);
var stream = new FrameRequestStream();
stream.StartAcceptingReads(body);
input.Add("Hello", true);
var buffer1 = new byte[1024];
Assert.Equal(0, await stream.ReadAsync(buffer1, 0, 1024));
}
}
[Fact]
public void Http11NoContentLengthConnectionClose()
{
using (var input = new TestInput())
{
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderConnection = "close" }, input.FrameContext);
var stream = new FrameRequestStream();
stream.StartAcceptingReads(body);
input.Add("Hello", true);
var buffer1 = new byte[1024];
Assert.Equal(0, stream.Read(buffer1, 0, 1024));
}
}
[Fact]
public async Task Http11NoContentLengthConnectionCloseAsync()
{
using (var input = new TestInput())
{
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderConnection = "close" }, input.FrameContext);
var stream = new FrameRequestStream();
stream.StartAcceptingReads(body);
input.Add("Hello", true);
var buffer1 = new byte[1024];
Assert.Equal(0, await stream.ReadAsync(buffer1, 0, 1024));
}
}
[Fact]
public async Task CanHandleLargeBlocks()
{
using (var input = new TestInput())
{
var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderContentLength = "8197" }, input.FrameContext);
var stream = new FrameRequestStream();
stream.StartAcceptingReads(body);
// Input needs to be greater than 4032 bytes to allocate a block not backed by a slab. // Input needs to be greater than 4032 bytes to allocate a block not backed by a slab.
var largeInput = new string('a', 8192); var largeInput = new string('a', 8192);
@ -101,8 +197,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
public static IEnumerable<object[]> RequestData => new[] public static IEnumerable<object[]> RequestData => new[]
{ {
// Remaining Data
new object[] { new FrameRequestHeaders { HeaderConnection = "close" }, new[] { "Hello ", "World!" } },
// Content-Length // Content-Length
new object[] { new FrameRequestHeaders { HeaderContentLength = "12" }, new[] { "Hello ", "World!" } }, new object[] { new FrameRequestHeaders { HeaderContentLength = "12" }, new[] { "Hello ", "World!" } },
// Chunked // Chunked