aspnetcore/src/Kestrel.Core/Internal/Http2/Http2Stream.cs

194 lines
7.3 KiB
C#

// 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;
using System.Buffers;
using System.IO;
using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
public partial class Http2Stream : HttpProtocol
{
private readonly Http2StreamContext _context;
private readonly Http2OutputProducer _http2Output;
private readonly Http2StreamOutputFlowControl _outputFlowControl;
private int _requestAborted;
public Http2Stream(Http2StreamContext context)
: base(context)
{
_context = context;
_outputFlowControl = new Http2StreamOutputFlowControl(context.ConnectionOutputFlowControl, context.ClientPeerSettings.InitialWindowSize);
_http2Output = new Http2OutputProducer(context.StreamId, context.FrameWriter, _outputFlowControl, context.TimeoutControl, context.MemoryPool);
Output = _http2Output;
}
public int StreamId => _context.StreamId;
public bool RequestBodyStarted { get; private set; }
public bool EndStreamReceived { get; private set; }
protected IHttp2StreamLifetimeHandler StreamLifetimeHandler => _context.StreamLifetimeHandler;
public override bool IsUpgradableRequest => false;
protected override void OnReset()
{
ResetIHttp2StreamIdFeature();
}
protected override void OnRequestProcessingEnded()
{
StreamLifetimeHandler.OnStreamCompleted(StreamId);
}
protected override string CreateRequestId()
=> StringUtilities.ConcatAsHexSuffix(ConnectionId, ':', (uint)StreamId);
protected override MessageBody CreateMessageBody()
=> Http2MessageBody.For(HttpRequestHeaders, this);
protected override bool TryParseRequest(ReadResult result, out bool endConnection)
{
// We don't need any of the parameters because we don't implement BeginRead to actually
// do the reading from a pipeline, nor do we use endConnection to report connection-level errors.
_httpVersion = Http.HttpVersion.Http2;
var methodText = RequestHeaders[HeaderNames.Method];
Method = HttpUtilities.GetKnownMethod(methodText);
_methodText = methodText;
if (!string.Equals(RequestHeaders[HeaderNames.Scheme], Scheme, StringComparison.OrdinalIgnoreCase))
{
BadHttpRequestException.Throw(RequestRejectionReason.InvalidRequestLine);
}
var path = RequestHeaders[HeaderNames.Path].ToString();
var queryIndex = path.IndexOf('?');
Path = queryIndex == -1 ? path : path.Substring(0, queryIndex);
QueryString = queryIndex == -1 ? string.Empty : path.Substring(queryIndex);
RawTarget = path;
// https://tools.ietf.org/html/rfc7230#section-5.4
// A server MUST respond with a 400 (Bad Request) status code to any
// HTTP/1.1 request message that lacks a Host header field and to any
// request message that contains more than one Host header field or a
// Host header field with an invalid field-value.
var authority = RequestHeaders[HeaderNames.Authority];
var host = HttpRequestHeaders.HeaderHost;
if (authority.Count > 0)
{
// https://tools.ietf.org/html/rfc7540#section-8.1.2.3
// An intermediary that converts an HTTP/2 request to HTTP/1.1 MUST
// create a Host header field if one is not present in a request by
// copying the value of the ":authority" pseudo - header field.
//
// We take this one step further, we don't want mismatched :authority
// and Host headers, replace Host if :authority is defined.
HttpRequestHeaders.HeaderHost = authority;
host = authority;
}
// TODO: OPTIONS * requests?
// To ensure that the HTTP / 1.1 request line can be reproduced
// accurately, this pseudo - header field MUST be omitted when
// translating from an HTTP/ 1.1 request that has a request target in
// origin or asterisk form(see[RFC7230], Section 5.3).
// https://tools.ietf.org/html/rfc7230#section-5.3
if (host.Count <= 0)
{
BadHttpRequestException.Throw(RequestRejectionReason.MissingHostHeader);
}
else if (host.Count > 1)
{
BadHttpRequestException.Throw(RequestRejectionReason.MultipleHostHeaders);
}
var hostText = host.ToString();
HttpUtilities.ValidateHostHeader(hostText);
endConnection = false;
return true;
}
public async Task OnDataAsync(ArraySegment<byte> data, bool endStream)
{
// TODO: content-length accounting
// TODO: flow-control
try
{
if (data.Count > 0)
{
RequestBodyPipe.Writer.Write(data);
RequestBodyStarted = true;
await RequestBodyPipe.Writer.FlushAsync();
}
if (endStream)
{
EndStreamReceived = true;
RequestBodyPipe.Writer.Complete();
}
}
catch (Exception ex)
{
RequestBodyPipe.Writer.Complete(ex);
}
}
public bool TryUpdateOutputWindow(int bytes)
{
return _context.FrameWriter.TryUpdateStreamWindow(_outputFlowControl, bytes);
}
public override void Abort(ConnectionAbortedException abortReason)
{
if (Interlocked.Exchange(ref _requestAborted, 1) != 0)
{
return;
}
AbortCore(abortReason);
}
protected override void ApplicationAbort()
{
Log.ApplicationAbortedConnection(ConnectionId, TraceIdentifier);
var abortReason = new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication);
ResetAndAbort(abortReason, Http2ErrorCode.CANCEL);
}
private void ResetAndAbort(ConnectionAbortedException abortReason, Http2ErrorCode error)
{
if (Interlocked.Exchange(ref _requestAborted, 1) != 0)
{
return;
}
// Don't block on IO. This never faults.
_ = _http2Output.WriteRstStreamAsync(error);
AbortCore(abortReason);
}
private void AbortCore(ConnectionAbortedException abortReason)
{
base.Abort(abortReason);
// Unblock the request body.
RequestBodyPipe.Writer.Complete(new IOException(CoreStrings.Http2StreamAborted, abortReason));
}
}
}