HTTP/2: implement 100-continue (#2106)
This commit is contained in:
parent
c57aa3b2a8
commit
3fbfba63f8
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue