194 lines
7.3 KiB
C#
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));
|
|
}
|
|
}
|
|
}
|