HTTP/2: implement 100-continue (#2106)

This commit is contained in:
Cesar Blum Silveira 2017-10-18 16:31:50 -07:00 committed by Stephen Halter
parent c57aa3b2a8
commit 3fbfba63f8
6 changed files with 74 additions and 12 deletions

View File

@ -14,7 +14,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
private readonly Http1Connection _context;
private bool _send100Continue = true;
private volatile bool _canceled;
private Task _pumpTask;
@ -150,15 +149,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
}
private void TryProduceContinue()
{
if (_send100Continue)
{
_context.HttpResponseControl.ProduceContinue();
_send100Continue = false;
}
}
protected void Copy(ReadableBuffer readableBuffer, WritableBuffer writableBuffer)
{
_context.TimeoutControl.BytesRead(readableBuffer.Length);

View File

@ -6,7 +6,6 @@ using System.IO;
using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
@ -18,6 +17,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private readonly HttpProtocol _context;
private bool _send100Continue = true;
protected MessageBody(HttpProtocol context)
{
_context = context;
@ -111,6 +112,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public abstract Task StopAsync();
protected void TryProduceContinue()
{
if (_send100Continue)
{
_context.HttpResponseControl.ProduceContinue();
_send100Continue = false;
}
}
private void TryInit()
{
if (!_context.HasStartedConsumingRequestBody)

View File

@ -14,6 +14,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
public class Http2FrameWriter : IHttp2FrameWriter
{
// Literal Header Field without Indexing - Indexed Name (Index 8 - :status)
private static readonly byte[] _continueBytes = new byte[] { 0x08, 0x03, (byte)'1', (byte)'0', (byte)'0' };
private readonly Http2Frame _outgoingFrame = new Http2Frame();
private readonly object _writeLock = new object();
private readonly HPackEncoder _hpackEncoder = new HPackEncoder();
@ -50,7 +53,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public Task Write100ContinueAsync(int streamId)
{
return Task.CompletedTask;
lock (_writeLock)
{
_outgoingFrame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS, streamId);
_outgoingFrame.Length = _continueBytes.Length;
_continueBytes.CopyTo(_outgoingFrame.HeadersPayload);
return WriteAsync(_outgoingFrame.Raw);
}
}
public void WriteResponseHeaders(int streamId, int statusCode, IHeaderDictionary headers)

View File

@ -16,6 +16,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_context = context;
}
protected override void OnReadStarted()
{
// Produce 100-continue if no request body data for the stream has arrived yet.
if (!_context.RequestBodyStarted)
{
TryProduceContinue();
}
}
protected override Task OnConsumeAsync() => Task.CompletedTask;
public override Task StopAsync()

View File

@ -24,6 +24,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public int StreamId => _context.StreamId;
public bool RequestBodyStarted { get; private set; }
public bool EndStreamReceived { get; private set; }
protected IHttp2StreamLifetimeHandler StreamLifetimeHandler => _context.StreamLifetimeHandler;
@ -84,6 +85,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
writableBuffer.Commit();
}
RequestBodyStarted = true;
await writableBuffer.FlushAsync();
}

View File

@ -33,6 +33,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
new KeyValuePair<string, string>(":scheme", "http"),
};
private static readonly IEnumerable<KeyValuePair<string, string>> _expectContinueRequestHeaders = new[]
{
new KeyValuePair<string, string>(":method", "POST"),
new KeyValuePair<string, string>(":path", "/"),
new KeyValuePair<string, string>(":authority", "127.0.0.1"),
new KeyValuePair<string, string>(":scheme", "https"),
new KeyValuePair<string, string>("expect", "100-continue"),
};
private static readonly IEnumerable<KeyValuePair<string, string>> _browserRequestHeaders = new[]
{
new KeyValuePair<string, string>(":method", "GET"),
@ -755,6 +764,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task HEADERS_Received_ContainsExpect100Continue_100ContinueSent()
{
await InitializeConnectionAsync(_echoApplication);
await StartStreamAsync(1, _expectContinueRequestHeaders, false);
var frame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 5,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await SendDataAsync(1, _helloBytes, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 5,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
Assert.Equal(new byte[] { 0x08, 0x03, (byte)'1', (byte)'0', (byte)'0' }, frame.HeadersPayload.ToArray());
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task HEADERS_Received_StreamIdZero_ConnectionError()
{