Put request trailers in a separate collection (#10410)
This commit is contained in:
parent
c2e2d7d135
commit
156c4feb65
|
|
@ -373,6 +373,13 @@ namespace Microsoft.AspNetCore.Http
|
||||||
public string ToUriComponent() { throw null; }
|
public string ToUriComponent() { throw null; }
|
||||||
}
|
}
|
||||||
public delegate System.Threading.Tasks.Task RequestDelegate(Microsoft.AspNetCore.Http.HttpContext context);
|
public delegate System.Threading.Tasks.Task RequestDelegate(Microsoft.AspNetCore.Http.HttpContext context);
|
||||||
|
public static partial class RequestTrailerExtensions
|
||||||
|
{
|
||||||
|
public static bool CheckTrailersAvailable(this Microsoft.AspNetCore.Http.HttpRequest request) { throw null; }
|
||||||
|
public static Microsoft.Extensions.Primitives.StringValues GetDeclaredTrailers(this Microsoft.AspNetCore.Http.HttpRequest request) { throw null; }
|
||||||
|
public static Microsoft.Extensions.Primitives.StringValues GetTrailer(this Microsoft.AspNetCore.Http.HttpRequest request, string trailerName) { throw null; }
|
||||||
|
public static bool SupportsTrailers(this Microsoft.AspNetCore.Http.HttpRequest request) { throw null; }
|
||||||
|
}
|
||||||
public static partial class ResponseTrailerExtensions
|
public static partial class ResponseTrailerExtensions
|
||||||
{
|
{
|
||||||
public static void AppendTrailer(this Microsoft.AspNetCore.Http.HttpResponse response, string trailerName, Microsoft.Extensions.Primitives.StringValues trailerValues) { }
|
public static void AppendTrailer(this Microsoft.AspNetCore.Http.HttpResponse response, string trailerName, Microsoft.Extensions.Primitives.StringValues trailerValues) { }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Http
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// HttpRequest extensions for working with request trailing headers.
|
||||||
|
/// </summary>
|
||||||
|
public static class RequestTrailerExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the request "Trailer" header that lists which trailers to expect after the body.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static StringValues GetDeclaredTrailers(this HttpRequest request)
|
||||||
|
{
|
||||||
|
return request.Headers.GetCommaSeparatedValues(HeaderNames.Trailer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if the request supports receiving trailer headers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool SupportsTrailers(this HttpRequest request)
|
||||||
|
{
|
||||||
|
return request.HttpContext.Features.Get<IHttpRequestTrailersFeature>() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the request supports trailers and they are available to be read now.
|
||||||
|
/// This does not mean that there are any trailers to read.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool CheckTrailersAvailable(this HttpRequest request)
|
||||||
|
{
|
||||||
|
return request.HttpContext.Features.Get<IHttpRequestTrailersFeature>()?.Available == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the requested trailing header from the response. Check <see cref="SupportsTrailers"/>
|
||||||
|
/// or a NotSupportedException may be thrown.
|
||||||
|
/// Check <see cref="CheckTrailersAvailable" /> or an InvalidOperationException may be thrown.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <param name="trailerName"></param>
|
||||||
|
public static StringValues GetTrailer(this HttpRequest request, string trailerName)
|
||||||
|
{
|
||||||
|
var feature = request.HttpContext.Features.Get<IHttpRequestTrailersFeature>();
|
||||||
|
if (feature == null)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("This request does not support trailers.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return feature.Trailers[trailerName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -195,6 +195,11 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
System.Threading.CancellationToken RequestAborted { get; set; }
|
System.Threading.CancellationToken RequestAborted { get; set; }
|
||||||
void Abort();
|
void Abort();
|
||||||
}
|
}
|
||||||
|
public partial interface IHttpRequestTrailersFeature
|
||||||
|
{
|
||||||
|
bool Available { get; }
|
||||||
|
Microsoft.AspNetCore.Http.IHeaderDictionary Trailers { get; }
|
||||||
|
}
|
||||||
public partial interface IHttpResponseFeature
|
public partial interface IHttpResponseFeature
|
||||||
{
|
{
|
||||||
System.IO.Stream Body { get; set; }
|
System.IO.Stream Body { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Http.Features
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This feature exposes HTTP request trailer headers, either for HTTP/1.1 chunked bodies or HTTP/2 trailing headers.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHttpRequestTrailersFeature
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if the <see cref="Trailers"/> are available yet. They may not be available until the
|
||||||
|
/// request body is fully read.
|
||||||
|
/// </summary>
|
||||||
|
bool Available { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The trailing headers received. This will throw <see cref="InvalidOperationException"/> if <see cref="Available"/>
|
||||||
|
/// returns false. They may not be available until the request body is fully read. If there are no trailers this will
|
||||||
|
/// return an empty collection.
|
||||||
|
/// </summary>
|
||||||
|
IHeaderDictionary Trailers { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -276,6 +276,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
public static string GetAsciiOrUTF8StringNonNullCharacters(this System.Span<byte> span) { throw null; }
|
public static string GetAsciiOrUTF8StringNonNullCharacters(this System.Span<byte> span) { throw null; }
|
||||||
public static string GetAsciiStringEscaped(this System.Span<byte> span, int maxChars) { throw null; }
|
public static string GetAsciiStringEscaped(this System.Span<byte> span, int maxChars) { throw null; }
|
||||||
public static string GetAsciiStringNonNullCharacters(this System.Span<byte> span) { throw null; }
|
public static string GetAsciiStringNonNullCharacters(this System.Span<byte> span) { throw null; }
|
||||||
|
public static string GetHeaderName(this System.Span<byte> span) { throw null; }
|
||||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public static bool GetKnownHttpScheme(this System.Span<byte> span, out Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpScheme knownScheme) { throw null; }
|
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public static bool GetKnownHttpScheme(this System.Span<byte> span, out Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpScheme knownScheme) { throw null; }
|
||||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public static bool GetKnownMethod(this System.Span<byte> span, out Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod method, out int length) { throw null; }
|
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public static bool GetKnownMethod(this System.Span<byte> span, out Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod method, out int length) { throw null; }
|
||||||
public static Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod GetKnownMethod(string value) { throw null; }
|
public static Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod GetKnownMethod(string value) { throw null; }
|
||||||
|
|
|
||||||
|
|
@ -602,4 +602,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
|
||||||
<data name="Http2MinDataRateNotSupported" xml:space="preserve">
|
<data name="Http2MinDataRateNotSupported" xml:space="preserve">
|
||||||
<value>This feature is not supported for HTTP/2 requests except to disable it entirely by setting the rate to null.</value>
|
<value>This feature is not supported for HTTP/2 requests except to disable it entirely by setting the rate to null.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="RequestTrailersNotAvailable" xml:space="preserve">
|
||||||
|
<value>The request trailers are not available yet. They may not be available until the full request body is read.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -35,7 +35,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
: base(context)
|
: base(context)
|
||||||
{
|
{
|
||||||
RequestKeepAlive = keepAlive;
|
RequestKeepAlive = keepAlive;
|
||||||
|
|
||||||
_requestBodyPipe = CreateRequestBodyPipe(context);
|
_requestBodyPipe = CreateRequestBodyPipe(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -301,7 +300,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
// _consumedBytes aren't tracked for trailer headers, since headers have separate limits.
|
// _consumedBytes aren't tracked for trailer headers, since headers have separate limits.
|
||||||
if (_mode == Mode.TrailerHeaders)
|
if (_mode == Mode.TrailerHeaders)
|
||||||
{
|
{
|
||||||
if (_context.TakeMessageHeaders(readableBuffer, out consumed, out examined))
|
if (_context.TakeMessageHeaders(readableBuffer, trailers: true, out consumed, out examined))
|
||||||
{
|
{
|
||||||
_mode = Mode.Complete;
|
_mode = Mode.Complete;
|
||||||
}
|
}
|
||||||
|
|
@ -489,6 +488,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
consumed = trailerBuffer.End;
|
consumed = trailerBuffer.End;
|
||||||
AddAndCheckConsumedBytes(2);
|
AddAndCheckConsumedBytes(2);
|
||||||
_mode = Mode.Complete;
|
_mode = Mode.Complete;
|
||||||
|
// No trailers
|
||||||
|
_context.OnTrailersComplete();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RequestProcessingStatus.ParsingHeaders:
|
case RequestProcessingStatus.ParsingHeaders:
|
||||||
if (TakeMessageHeaders(buffer, out consumed, out examined))
|
if (TakeMessageHeaders(buffer, trailers: false, out consumed, out examined))
|
||||||
{
|
{
|
||||||
_requestProcessingStatus = RequestProcessingStatus.AppStarted;
|
_requestProcessingStatus = RequestProcessingStatus.AppStarted;
|
||||||
}
|
}
|
||||||
|
|
@ -189,7 +189,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TakeMessageHeaders(ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
|
public bool TakeMessageHeaders(ReadOnlySequence<byte> buffer, bool trailers, out SequencePosition consumed, out SequencePosition examined)
|
||||||
{
|
{
|
||||||
// Make sure the buffer is limited
|
// Make sure the buffer is limited
|
||||||
bool overLength = false;
|
bool overLength = false;
|
||||||
|
|
@ -202,7 +202,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
overLength = true;
|
overLength = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = _parser.ParseHeaders(new Http1ParsingHandler(this), buffer, out consumed, out examined, out var consumedBytes);
|
var result = _parser.ParseHeaders(new Http1ParsingHandler(this, trailers), buffer, out consumed, out examined, out var consumedBytes);
|
||||||
_remainingRequestHeadersBytesAllowed -= consumedBytes;
|
_remainingRequestHeadersBytesAllowed -= consumedBytes;
|
||||||
|
|
||||||
if (!result && overLength)
|
if (!result && overLength)
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
_context.Input.AdvanceTo(consumed);
|
_context.Input.AdvanceTo(consumed);
|
||||||
_finalAdvanceCalled = true;
|
_finalAdvanceCalled = true;
|
||||||
|
_context.OnTrailersComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
BadHttpRequestException.Throw(RequestRejectionReason.UpgradeRequestCannotHavePayload);
|
BadHttpRequestException.Throw(RequestRejectionReason.UpgradeRequestCannotHavePayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.OnTrailersComplete(); // No trailers for these.
|
||||||
return new Http1UpgradeMessageBody(context);
|
return new Http1UpgradeMessageBody(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,6 +174,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
BadHttpRequestException.Throw(requestRejectionReason, context.Method);
|
BadHttpRequestException.Throw(requestRejectionReason, context.Method);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.OnTrailersComplete(); // No trailers for these.
|
||||||
return keepAlive ? MessageBody.ZeroContentLengthKeepAlive : MessageBody.ZeroContentLengthClose;
|
return keepAlive ? MessageBody.ZeroContentLengthKeepAlive : MessageBody.ZeroContentLengthClose;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,17 +8,43 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
internal readonly struct Http1ParsingHandler : IHttpRequestLineHandler, IHttpHeadersHandler
|
internal readonly struct Http1ParsingHandler : IHttpRequestLineHandler, IHttpHeadersHandler
|
||||||
{
|
{
|
||||||
public readonly Http1Connection Connection;
|
public readonly Http1Connection Connection;
|
||||||
|
public readonly bool Trailers;
|
||||||
|
|
||||||
public Http1ParsingHandler(Http1Connection connection)
|
public Http1ParsingHandler(Http1Connection connection)
|
||||||
{
|
{
|
||||||
Connection = connection;
|
Connection = connection;
|
||||||
|
Trailers = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Http1ParsingHandler(Http1Connection connection, bool trailers)
|
||||||
|
{
|
||||||
|
Connection = connection;
|
||||||
|
Trailers = trailers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnHeader(Span<byte> name, Span<byte> value)
|
public void OnHeader(Span<byte> name, Span<byte> value)
|
||||||
=> Connection.OnHeader(name, value);
|
{
|
||||||
|
if (Trailers)
|
||||||
|
{
|
||||||
|
Connection.OnTrailer(name, value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Connection.OnHeader(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void OnHeadersComplete()
|
public void OnHeadersComplete()
|
||||||
=> Connection.OnHeadersComplete();
|
{
|
||||||
|
if (Trailers)
|
||||||
|
{
|
||||||
|
Connection.OnTrailersComplete();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Connection.OnHeadersComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
|
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
|
||||||
=> Connection.OnStartLine(method, version, target, path, query, customMethod, pathEncoded);
|
=> Connection.OnStartLine(method, version, target, path, query, customMethod, pathEncoded);
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
IHttpConnectionFeature,
|
IHttpConnectionFeature,
|
||||||
IHttpRequestLifetimeFeature,
|
IHttpRequestLifetimeFeature,
|
||||||
IHttpRequestIdentifierFeature,
|
IHttpRequestIdentifierFeature,
|
||||||
|
IHttpRequestTrailersFeature,
|
||||||
IHttpBodyControlFeature,
|
IHttpBodyControlFeature,
|
||||||
IHttpMaxRequestBodySizeFeature,
|
IHttpMaxRequestBodySizeFeature,
|
||||||
IHttpResponseStartFeature,
|
IHttpResponseStartFeature,
|
||||||
|
|
@ -133,6 +134,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IHttpRequestTrailersFeature.Available => RequestTrailersAvailable;
|
||||||
|
|
||||||
|
IHeaderDictionary IHttpRequestTrailersFeature.Trailers
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!RequestTrailersAvailable)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(CoreStrings.RequestTrailersNotAvailable);
|
||||||
|
}
|
||||||
|
return RequestTrailers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int IHttpResponseFeature.StatusCode
|
int IHttpResponseFeature.StatusCode
|
||||||
{
|
{
|
||||||
get => StatusCode;
|
get => StatusCode;
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
private static readonly Type IRouteValuesFeatureType = typeof(IRouteValuesFeature);
|
private static readonly Type IRouteValuesFeatureType = typeof(IRouteValuesFeature);
|
||||||
private static readonly Type IEndpointFeatureType = typeof(IEndpointFeature);
|
private static readonly Type IEndpointFeatureType = typeof(IEndpointFeature);
|
||||||
private static readonly Type IHttpAuthenticationFeatureType = typeof(IHttpAuthenticationFeature);
|
private static readonly Type IHttpAuthenticationFeatureType = typeof(IHttpAuthenticationFeature);
|
||||||
|
private static readonly Type IHttpRequestTrailersFeatureType = typeof(IHttpRequestTrailersFeature);
|
||||||
private static readonly Type IQueryFeatureType = typeof(IQueryFeature);
|
private static readonly Type IQueryFeatureType = typeof(IQueryFeature);
|
||||||
private static readonly Type IFormFeatureType = typeof(IFormFeature);
|
private static readonly Type IFormFeatureType = typeof(IFormFeature);
|
||||||
private static readonly Type IHttpUpgradeFeatureType = typeof(IHttpUpgradeFeature);
|
private static readonly Type IHttpUpgradeFeatureType = typeof(IHttpUpgradeFeature);
|
||||||
|
|
@ -52,6 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
private object _currentIRouteValuesFeature;
|
private object _currentIRouteValuesFeature;
|
||||||
private object _currentIEndpointFeature;
|
private object _currentIEndpointFeature;
|
||||||
private object _currentIHttpAuthenticationFeature;
|
private object _currentIHttpAuthenticationFeature;
|
||||||
|
private object _currentIHttpRequestTrailersFeature;
|
||||||
private object _currentIQueryFeature;
|
private object _currentIQueryFeature;
|
||||||
private object _currentIFormFeature;
|
private object _currentIFormFeature;
|
||||||
private object _currentIHttpUpgradeFeature;
|
private object _currentIHttpUpgradeFeature;
|
||||||
|
|
@ -82,6 +84,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
_currentIHttpUpgradeFeature = this;
|
_currentIHttpUpgradeFeature = this;
|
||||||
_currentIHttpRequestIdentifierFeature = this;
|
_currentIHttpRequestIdentifierFeature = this;
|
||||||
_currentIHttpRequestLifetimeFeature = this;
|
_currentIHttpRequestLifetimeFeature = this;
|
||||||
|
_currentIHttpRequestTrailersFeature = this;
|
||||||
_currentIHttpConnectionFeature = this;
|
_currentIHttpConnectionFeature = this;
|
||||||
_currentIHttpMaxRequestBodySizeFeature = this;
|
_currentIHttpMaxRequestBodySizeFeature = this;
|
||||||
_currentIHttpMinRequestBodyDataRateFeature = this;
|
_currentIHttpMinRequestBodyDataRateFeature = this;
|
||||||
|
|
@ -201,6 +204,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
feature = _currentIHttpAuthenticationFeature;
|
feature = _currentIHttpAuthenticationFeature;
|
||||||
}
|
}
|
||||||
|
else if (key == IHttpRequestTrailersFeatureType)
|
||||||
|
{
|
||||||
|
feature = _currentIHttpRequestTrailersFeature;
|
||||||
|
}
|
||||||
else if (key == IQueryFeatureType)
|
else if (key == IQueryFeatureType)
|
||||||
{
|
{
|
||||||
feature = _currentIQueryFeature;
|
feature = _currentIQueryFeature;
|
||||||
|
|
@ -321,6 +328,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
_currentIHttpAuthenticationFeature = value;
|
_currentIHttpAuthenticationFeature = value;
|
||||||
}
|
}
|
||||||
|
else if (key == IHttpRequestTrailersFeatureType)
|
||||||
|
{
|
||||||
|
_currentIHttpRequestTrailersFeature = value;
|
||||||
|
}
|
||||||
else if (key == IQueryFeatureType)
|
else if (key == IQueryFeatureType)
|
||||||
{
|
{
|
||||||
_currentIQueryFeature = value;
|
_currentIQueryFeature = value;
|
||||||
|
|
@ -439,6 +450,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
feature = (TFeature)_currentIHttpAuthenticationFeature;
|
feature = (TFeature)_currentIHttpAuthenticationFeature;
|
||||||
}
|
}
|
||||||
|
else if (typeof(TFeature) == typeof(IHttpRequestTrailersFeature))
|
||||||
|
{
|
||||||
|
feature = (TFeature)_currentIHttpRequestTrailersFeature;
|
||||||
|
}
|
||||||
else if (typeof(TFeature) == typeof(IQueryFeature))
|
else if (typeof(TFeature) == typeof(IQueryFeature))
|
||||||
{
|
{
|
||||||
feature = (TFeature)_currentIQueryFeature;
|
feature = (TFeature)_currentIQueryFeature;
|
||||||
|
|
@ -563,6 +578,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
_currentIHttpAuthenticationFeature = feature;
|
_currentIHttpAuthenticationFeature = feature;
|
||||||
}
|
}
|
||||||
|
else if (typeof(TFeature) == typeof(IHttpRequestTrailersFeature))
|
||||||
|
{
|
||||||
|
_currentIHttpRequestTrailersFeature = feature;
|
||||||
|
}
|
||||||
else if (typeof(TFeature) == typeof(IQueryFeature))
|
else if (typeof(TFeature) == typeof(IQueryFeature))
|
||||||
{
|
{
|
||||||
_currentIQueryFeature = feature;
|
_currentIQueryFeature = feature;
|
||||||
|
|
@ -679,6 +698,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
yield return new KeyValuePair<Type, object>(IHttpAuthenticationFeatureType, _currentIHttpAuthenticationFeature);
|
yield return new KeyValuePair<Type, object>(IHttpAuthenticationFeatureType, _currentIHttpAuthenticationFeature);
|
||||||
}
|
}
|
||||||
|
if (_currentIHttpRequestTrailersFeature != null)
|
||||||
|
{
|
||||||
|
yield return new KeyValuePair<Type, object>(IHttpRequestTrailersFeatureType, _currentIHttpRequestTrailersFeature);
|
||||||
|
}
|
||||||
if (_currentIQueryFeature != null)
|
if (_currentIQueryFeature != null)
|
||||||
{
|
{
|
||||||
yield return new KeyValuePair<Type, object>(IQueryFeatureType, _currentIQueryFeature);
|
yield return new KeyValuePair<Type, object>(IQueryFeatureType, _currentIQueryFeature);
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
}
|
}
|
||||||
|
|
||||||
public IHeaderDictionary RequestHeaders { get; set; }
|
public IHeaderDictionary RequestHeaders { get; set; }
|
||||||
|
public IHeaderDictionary RequestTrailers { get; } = new HeaderDictionary();
|
||||||
|
public bool RequestTrailersAvailable { get; set; }
|
||||||
public Stream RequestBody { get; set; }
|
public Stream RequestBody { get; set; }
|
||||||
public PipeReader RequestBodyPipeReader { get; set; }
|
public PipeReader RequestBodyPipeReader { get; set; }
|
||||||
|
|
||||||
|
|
@ -369,6 +371,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
HttpResponseHeaders.Reset();
|
HttpResponseHeaders.Reset();
|
||||||
RequestHeaders = HttpRequestHeaders;
|
RequestHeaders = HttpRequestHeaders;
|
||||||
ResponseHeaders = HttpResponseHeaders;
|
ResponseHeaders = HttpResponseHeaders;
|
||||||
|
RequestTrailers.Clear();
|
||||||
|
RequestTrailersAvailable = false;
|
||||||
|
|
||||||
_isLeasedMemoryInvalid = true;
|
_isLeasedMemoryInvalid = true;
|
||||||
_hasAdvanced = false;
|
_hasAdvanced = false;
|
||||||
|
|
@ -524,11 +528,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
HttpRequestHeaders.Append(name, value);
|
HttpRequestHeaders.Append(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnTrailer(Span<byte> name, Span<byte> value)
|
||||||
|
{
|
||||||
|
// Trailers still count towards the limit.
|
||||||
|
_requestHeadersParsed++;
|
||||||
|
if (_requestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount)
|
||||||
|
{
|
||||||
|
BadHttpRequestException.Throw(RequestRejectionReason.TooManyHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
|
string key = name.GetHeaderName();
|
||||||
|
var valueStr = value.GetAsciiOrUTF8StringNonNullCharacters();
|
||||||
|
RequestTrailers.Append(key, valueStr);
|
||||||
|
}
|
||||||
|
|
||||||
public void OnHeadersComplete()
|
public void OnHeadersComplete()
|
||||||
{
|
{
|
||||||
HttpRequestHeaders.OnHeadersComplete();
|
HttpRequestHeaders.OnHeadersComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnTrailersComplete()
|
||||||
|
{
|
||||||
|
RequestTrailersAvailable = true;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application)
|
public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -103,16 +103,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
private unsafe void AppendUnknownHeaders(Span<byte> name, string valueString)
|
private unsafe void AppendUnknownHeaders(Span<byte> name, string valueString)
|
||||||
{
|
{
|
||||||
string key = new string('\0', name.Length);
|
string key = name.GetHeaderName();
|
||||||
fixed (byte* pKeyBytes = name)
|
|
||||||
fixed (char* keyBuffer = key)
|
|
||||||
{
|
|
||||||
if (!StringUtilities.TryGetAsciiString(pKeyBytes, keyBuffer, name.Length))
|
|
||||||
{
|
|
||||||
BadHttpRequestException.Throw(RequestRejectionReason.InvalidCharactersInHeaderName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Unknown.TryGetValue(key, out var existing);
|
Unknown.TryGetValue(key, out var existing);
|
||||||
Unknown[key] = AppendValue(existing, valueString);
|
Unknown[key] = AppendValue(existing, valueString);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1073,9 +1073,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
ValidateHeader(name, value);
|
ValidateHeader(name, value);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Drop trailers for now. Adding them to the request headers is not thread safe.
|
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
|
||||||
// https://github.com/aspnet/KestrelHttpServer/issues/2051
|
{
|
||||||
if (_requestHeaderParsingState != RequestHeaderParsingState.Trailers)
|
_currentHeadersStream.OnTrailer(name, value);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
// Throws BadRequest for header count limit breaches.
|
// Throws BadRequest for header count limit breaches.
|
||||||
// Throws InvalidOperation for bad encoding.
|
// Throws InvalidOperation for bad encoding.
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnTrailersComplete();
|
||||||
RequestBodyPipe.Writer.Complete();
|
RequestBodyPipe.Writer.Complete();
|
||||||
|
|
||||||
_inputFlowControl.StopWindowUpdates();
|
_inputFlowControl.StopWindowUpdates();
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The same as GetAsciiStringNonNullCharacters but throws BadRequest
|
||||||
|
public static unsafe string GetHeaderName(this Span<byte> span)
|
||||||
|
{
|
||||||
|
if (span.IsEmpty)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var asciiString = new string('\0', span.Length);
|
||||||
|
|
||||||
|
fixed (char* output = asciiString)
|
||||||
|
fixed (byte* buffer = span)
|
||||||
|
{
|
||||||
|
// This version if AsciiUtilities returns null if there are any null (0 byte) characters
|
||||||
|
// in the string
|
||||||
|
if (!StringUtilities.TryGetAsciiString(buffer, output, span.Length))
|
||||||
|
{
|
||||||
|
BadHttpRequestException.Throw(RequestRejectionReason.InvalidCharactersInHeaderName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return asciiString;
|
||||||
|
}
|
||||||
|
|
||||||
public static unsafe string GetAsciiStringNonNullCharacters(this Span<byte> span)
|
public static unsafe string GetAsciiStringNonNullCharacters(this Span<byte> span)
|
||||||
{
|
{
|
||||||
if (span.IsEmpty)
|
if (span.IsEmpty)
|
||||||
|
|
|
||||||
|
|
@ -2268,6 +2268,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
internal static string FormatHttp2MinDataRateNotSupported()
|
internal static string FormatHttp2MinDataRateNotSupported()
|
||||||
=> GetString("Http2MinDataRateNotSupported");
|
=> GetString("Http2MinDataRateNotSupported");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The request trailers are not available yet. They may not be available until the full request body is read.
|
||||||
|
/// </summary>
|
||||||
|
internal static string RequestTrailersNotAvailable
|
||||||
|
{
|
||||||
|
get => GetString("RequestTrailersNotAvailable");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The request trailers are not available yet. They may not be available until the full request body is read.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatRequestTrailersNotAvailable()
|
||||||
|
=> GetString("RequestTrailersNotAvailable");
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
await _application.Output.WriteAsync(Encoding.UTF8.GetBytes("\r\n\r\n"));
|
await _application.Output.WriteAsync(Encoding.UTF8.GetBytes("\r\n\r\n"));
|
||||||
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
||||||
|
|
||||||
_http1Connection.TakeMessageHeaders(readableBuffer, out _consumed, out _examined);
|
_http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined);
|
||||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||||
|
|
||||||
Assert.Equal(headerValue, _http1Connection.RequestHeaders[headerName]);
|
Assert.Equal(headerValue, _http1Connection.RequestHeaders[headerName]);
|
||||||
|
|
@ -116,7 +116,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
await _application.Output.WriteAsync(extendedAsciiEncoding.GetBytes("\r\n\r\n"));
|
await _application.Output.WriteAsync(extendedAsciiEncoding.GetBytes("\r\n\r\n"));
|
||||||
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
||||||
|
|
||||||
var exception = Assert.Throws<InvalidOperationException>(() => _http1Connection.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
|
var exception = Assert.Throws<InvalidOperationException>(() => _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -129,7 +129,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine}\r\n"));
|
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine}\r\n"));
|
||||||
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
||||||
|
|
||||||
var exception = Assert.Throws<BadHttpRequestException>(() => _http1Connection.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
|
var exception = Assert.Throws<BadHttpRequestException>(() => _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined));
|
||||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||||
|
|
||||||
Assert.Equal(CoreStrings.BadRequest_HeadersExceedMaxTotalSize, exception.Message);
|
Assert.Equal(CoreStrings.BadRequest_HeadersExceedMaxTotalSize, exception.Message);
|
||||||
|
|
@ -145,7 +145,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLines}\r\n"));
|
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLines}\r\n"));
|
||||||
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
||||||
|
|
||||||
var exception = Assert.Throws<BadHttpRequestException>(() => _http1Connection.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
|
var exception = Assert.Throws<BadHttpRequestException>(() => _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined));
|
||||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||||
|
|
||||||
Assert.Equal(CoreStrings.BadRequest_TooManyHeaders, exception.Message);
|
Assert.Equal(CoreStrings.BadRequest_TooManyHeaders, exception.Message);
|
||||||
|
|
@ -250,7 +250,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine1}\r\n"));
|
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine1}\r\n"));
|
||||||
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
||||||
|
|
||||||
var takeMessageHeaders = _http1Connection.TakeMessageHeaders(readableBuffer, out _consumed, out _examined);
|
var takeMessageHeaders = _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined);
|
||||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||||
|
|
||||||
Assert.True(takeMessageHeaders);
|
Assert.True(takeMessageHeaders);
|
||||||
|
|
@ -262,7 +262,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine2}\r\n"));
|
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine2}\r\n"));
|
||||||
readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
||||||
|
|
||||||
takeMessageHeaders = _http1Connection.TakeMessageHeaders(readableBuffer, out _consumed, out _examined);
|
takeMessageHeaders = _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined);
|
||||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||||
|
|
||||||
Assert.True(takeMessageHeaders);
|
Assert.True(takeMessageHeaders);
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
_collection[typeof(IRequestBodyPipeFeature)] = CreateHttp1Connection();
|
_collection[typeof(IRequestBodyPipeFeature)] = CreateHttp1Connection();
|
||||||
_collection[typeof(IHttpRequestIdentifierFeature)] = CreateHttp1Connection();
|
_collection[typeof(IHttpRequestIdentifierFeature)] = CreateHttp1Connection();
|
||||||
_collection[typeof(IHttpRequestLifetimeFeature)] = CreateHttp1Connection();
|
_collection[typeof(IHttpRequestLifetimeFeature)] = CreateHttp1Connection();
|
||||||
|
_collection[typeof(IHttpRequestTrailersFeature)] = CreateHttp1Connection();
|
||||||
_collection[typeof(IHttpConnectionFeature)] = CreateHttp1Connection();
|
_collection[typeof(IHttpConnectionFeature)] = CreateHttp1Connection();
|
||||||
_collection[typeof(IHttpMaxRequestBodySizeFeature)] = CreateHttp1Connection();
|
_collection[typeof(IHttpMaxRequestBodySizeFeature)] = CreateHttp1Connection();
|
||||||
_collection[typeof(IHttpMinRequestBodyDataRateFeature)] = CreateHttp1Connection();
|
_collection[typeof(IHttpMinRequestBodyDataRateFeature)] = CreateHttp1Connection();
|
||||||
|
|
@ -144,6 +145,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
_collection.Set<IRequestBodyPipeFeature>(CreateHttp1Connection());
|
_collection.Set<IRequestBodyPipeFeature>(CreateHttp1Connection());
|
||||||
_collection.Set<IHttpRequestIdentifierFeature>(CreateHttp1Connection());
|
_collection.Set<IHttpRequestIdentifierFeature>(CreateHttp1Connection());
|
||||||
_collection.Set<IHttpRequestLifetimeFeature>(CreateHttp1Connection());
|
_collection.Set<IHttpRequestLifetimeFeature>(CreateHttp1Connection());
|
||||||
|
_collection.Set<IHttpRequestTrailersFeature>(CreateHttp1Connection());
|
||||||
_collection.Set<IHttpConnectionFeature>(CreateHttp1Connection());
|
_collection.Set<IHttpConnectionFeature>(CreateHttp1Connection());
|
||||||
_collection.Set<IHttpMaxRequestBodySizeFeature>(CreateHttp1Connection());
|
_collection.Set<IHttpMaxRequestBodySizeFeature>(CreateHttp1Connection());
|
||||||
_collection.Set<IHttpMinRequestBodyDataRateFeature>(CreateHttp1Connection());
|
_collection.Set<IHttpMinRequestBodyDataRateFeature>(CreateHttp1Connection());
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
ErrorUtilities.ThrowInvalidRequestLine();
|
ErrorUtilities.ThrowInvalidRequestLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_http1Connection.TakeMessageHeaders(_buffer, out consumed, out examined))
|
if (!_http1Connection.TakeMessageHeaders(_buffer, trailers: false, out consumed, out examined))
|
||||||
{
|
{
|
||||||
ErrorUtilities.ThrowInvalidRequestHeaders();
|
ErrorUtilities.ThrowInvalidRequestHeaders();
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +103,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
{
|
{
|
||||||
_http1Connection.Reset();
|
_http1Connection.Reset();
|
||||||
|
|
||||||
if (!_http1Connection.TakeMessageHeaders(_buffer, out var consumed, out var examined))
|
if (!_http1Connection.TakeMessageHeaders(_buffer, trailers: false, out var consumed, out var examined))
|
||||||
{
|
{
|
||||||
ErrorUtilities.ThrowInvalidRequestHeaders();
|
ErrorUtilities.ThrowInvalidRequestHeaders();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
|
|
||||||
readableBuffer = readableBuffer.Slice(consumed);
|
readableBuffer = readableBuffer.Slice(consumed);
|
||||||
|
|
||||||
if (!Http1Connection.TakeMessageHeaders(readableBuffer, out consumed, out examined))
|
if (!Http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out consumed, out examined))
|
||||||
{
|
{
|
||||||
ErrorUtilities.ThrowInvalidRequestHeaders();
|
ErrorUtilities.ThrowInvalidRequestHeaders();
|
||||||
}
|
}
|
||||||
|
|
@ -196,7 +196,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
result = Pipe.Reader.ReadAsync().GetAwaiter().GetResult();
|
result = Pipe.Reader.ReadAsync().GetAwaiter().GetResult();
|
||||||
readableBuffer = result.Buffer;
|
readableBuffer = result.Buffer;
|
||||||
|
|
||||||
if (!Http1Connection.TakeMessageHeaders(readableBuffer, out consumed, out examined))
|
if (!Http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out consumed, out examined))
|
||||||
{
|
{
|
||||||
ErrorUtilities.ThrowInvalidRequestHeaders();
|
ErrorUtilities.ThrowInvalidRequestHeaders();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -232,18 +232,59 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
|
|
||||||
var buffer = new byte[200];
|
var buffer = new byte[200];
|
||||||
|
|
||||||
|
// The first request is chunked with no trailers.
|
||||||
|
if (requestsReceived == 0)
|
||||||
|
{
|
||||||
|
Assert.True(request.SupportsTrailers(), "SupportsTrailers");
|
||||||
|
Assert.False(request.CheckTrailersAvailable(), "CheckTrailersAvailable"); // Not yet
|
||||||
|
Assert.Throws<InvalidOperationException>(() => request.GetTrailer("X-Trailer-Header")); // Not yet
|
||||||
|
}
|
||||||
|
// The middle requests are chunked with trailers.
|
||||||
|
else if (requestsReceived < requestCount)
|
||||||
|
{
|
||||||
|
Assert.True(request.SupportsTrailers(), "SupportsTrailers");
|
||||||
|
Assert.False(request.CheckTrailersAvailable(), "CheckTrailersAvailable"); // Not yet
|
||||||
|
Assert.Throws<InvalidOperationException>(() => request.GetTrailer("X-Trailer-Header")); // Not yet
|
||||||
|
Assert.Equal("X-Trailer-Header", request.GetDeclaredTrailers().ToString());
|
||||||
|
}
|
||||||
|
// The last request is content-length with no trailers.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.True(request.SupportsTrailers(), "SupportsTrailers");
|
||||||
|
Assert.False(request.CheckTrailersAvailable(), "CheckTrailersAvailable");
|
||||||
|
Assert.Throws<InvalidOperationException>(() => request.GetTrailer("X-Trailer-Header"));
|
||||||
|
}
|
||||||
|
|
||||||
while (await request.Body.ReadAsync(buffer, 0, buffer.Length) != 0)
|
while (await request.Body.ReadAsync(buffer, 0, buffer.Length) != 0)
|
||||||
{
|
{
|
||||||
;// read to end
|
;// read to end
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestsReceived < requestCount)
|
Assert.False(request.Headers.ContainsKey("X-Trailer-Header"));
|
||||||
|
|
||||||
|
// The first request is chunked with no trailers.
|
||||||
|
if (requestsReceived == 0)
|
||||||
{
|
{
|
||||||
Assert.Equal(new string('a', requestsReceived), request.Headers["X-Trailer-Header"].ToString());
|
Assert.True(request.SupportsTrailers(), "SupportsTrailers");
|
||||||
|
Assert.True(request.CheckTrailersAvailable(), "CheckTrailersAvailable");
|
||||||
|
Assert.Equal(string.Empty, request.GetDeclaredTrailers().ToString());
|
||||||
|
Assert.Equal(string.Empty, request.GetTrailer("X-Trailer-Header").ToString());
|
||||||
}
|
}
|
||||||
|
// The middle requests are chunked with trailers.
|
||||||
|
else if (requestsReceived < requestCount)
|
||||||
|
{
|
||||||
|
Assert.True(request.SupportsTrailers(), "SupportsTrailers");
|
||||||
|
Assert.True(request.CheckTrailersAvailable(), "CheckTrailersAvailable");
|
||||||
|
Assert.Equal("X-Trailer-Header", request.GetDeclaredTrailers().ToString());
|
||||||
|
Assert.Equal(new string('a', requestsReceived), request.GetTrailer("X-Trailer-Header").ToString());
|
||||||
|
}
|
||||||
|
// The last request is content-length with no trailers.
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Assert.True(string.IsNullOrEmpty(request.Headers["X-Trailer-Header"]));
|
Assert.True(request.SupportsTrailers(), "SupportsTrailers");
|
||||||
|
Assert.True(request.CheckTrailersAvailable(), "CheckTrailersAvailable");
|
||||||
|
Assert.Equal(string.Empty, request.GetDeclaredTrailers().ToString());
|
||||||
|
Assert.Equal(string.Empty, request.GetTrailer("X-Trailer-Header").ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
requestsReceived++;
|
requestsReceived++;
|
||||||
|
|
@ -278,6 +319,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
"POST / HTTP/1.1",
|
"POST / HTTP/1.1",
|
||||||
"Host:",
|
"Host:",
|
||||||
"Transfer-Encoding: chunked",
|
"Transfer-Encoding: chunked",
|
||||||
|
"Trailer: X-Trailer-Header",
|
||||||
"",
|
"",
|
||||||
"C",
|
"C",
|
||||||
$"HelloChunk{i:00}",
|
$"HelloChunk{i:00}",
|
||||||
|
|
@ -315,6 +357,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
var request = httpContext.Request;
|
var request = httpContext.Request;
|
||||||
|
|
||||||
|
// The first request is chunked with no trailers.
|
||||||
|
if (requestsReceived == 0)
|
||||||
|
{
|
||||||
|
Assert.True(request.SupportsTrailers(), "SupportsTrailers");
|
||||||
|
Assert.False(request.CheckTrailersAvailable(), "CheckTrailersAvailable"); // Not yet
|
||||||
|
Assert.Throws<InvalidOperationException>(() => request.GetTrailer("X-Trailer-Header")); // Not yet
|
||||||
|
}
|
||||||
|
// The middle requests are chunked with trailers.
|
||||||
|
else if (requestsReceived < requestCount)
|
||||||
|
{
|
||||||
|
Assert.True(request.SupportsTrailers(), "SupportsTrailers");
|
||||||
|
Assert.False(request.CheckTrailersAvailable(), "CheckTrailersAvailable"); // Not yet
|
||||||
|
Assert.Throws<InvalidOperationException>(() => request.GetTrailer("X-Trailer-Header")); // Not yet
|
||||||
|
Assert.Equal("X-Trailer-Header", request.GetDeclaredTrailers().ToString());
|
||||||
|
}
|
||||||
|
// The last request is content-length with no trailers.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.True(request.SupportsTrailers(), "SupportsTrailers");
|
||||||
|
Assert.False(request.CheckTrailersAvailable(), "CheckTrailersAvailable");
|
||||||
|
Assert.Throws<InvalidOperationException>(() => request.GetTrailer("X-Trailer-Header"));
|
||||||
|
}
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var result = await request.BodyReader.ReadAsync();
|
var result = await request.BodyReader.ReadAsync();
|
||||||
|
|
@ -325,13 +390,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestsReceived < requestCount)
|
Assert.False(request.Headers.ContainsKey("X-Trailer-Header"));
|
||||||
|
|
||||||
|
// The first request is chunked with no trailers.
|
||||||
|
if (requestsReceived == 0)
|
||||||
{
|
{
|
||||||
Assert.Equal(new string('a', requestsReceived), request.Headers["X-Trailer-Header"].ToString());
|
Assert.True(request.SupportsTrailers(), "SupportsTrailers");
|
||||||
|
Assert.True(request.CheckTrailersAvailable(), "CheckTrailersAvailable");
|
||||||
|
Assert.Equal(string.Empty, request.GetDeclaredTrailers().ToString());
|
||||||
|
Assert.Equal(string.Empty, request.GetTrailer("X-Trailer-Header").ToString());
|
||||||
}
|
}
|
||||||
|
// The middle requests are chunked with trailers.
|
||||||
|
else if (requestsReceived < requestCount)
|
||||||
|
{
|
||||||
|
Assert.True(request.SupportsTrailers(), "SupportsTrailers");
|
||||||
|
Assert.True(request.CheckTrailersAvailable(), "CheckTrailersAvailable");
|
||||||
|
Assert.Equal("X-Trailer-Header", request.GetDeclaredTrailers().ToString());
|
||||||
|
Assert.Equal(new string('a', requestsReceived), request.GetTrailer("X-Trailer-Header").ToString());
|
||||||
|
}
|
||||||
|
// The last request is content-length with no trailers.
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Assert.True(string.IsNullOrEmpty(request.Headers["X-Trailer-Header"]));
|
Assert.True(request.SupportsTrailers(), "SupportsTrailers");
|
||||||
|
Assert.True(request.CheckTrailersAvailable(), "CheckTrailersAvailable");
|
||||||
|
Assert.Equal(string.Empty, request.GetDeclaredTrailers().ToString());
|
||||||
|
Assert.Equal(string.Empty, request.GetTrailer("X-Trailer-Header").ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
requestsReceived++;
|
requestsReceived++;
|
||||||
|
|
@ -366,6 +449,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
"POST / HTTP/1.1",
|
"POST / HTTP/1.1",
|
||||||
"Host:",
|
"Host:",
|
||||||
"Transfer-Encoding: chunked",
|
"Transfer-Encoding: chunked",
|
||||||
|
"Trailer: X-Trailer-Header",
|
||||||
"",
|
"",
|
||||||
"C",
|
"C",
|
||||||
$"HelloChunk{i:00}",
|
$"HelloChunk{i:00}",
|
||||||
|
|
@ -495,13 +579,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
;// read to end
|
;// read to end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Assert.True(string.IsNullOrEmpty(request.Headers["X-Trailer-Header"]));
|
||||||
|
|
||||||
if (requestsReceived < requestCount)
|
if (requestsReceived < requestCount)
|
||||||
{
|
{
|
||||||
Assert.Equal(new string('a', requestsReceived), request.Headers["X-Trailer-Header"].ToString());
|
Assert.Equal(new string('a', requestsReceived), request.GetTrailer("X-Trailer-Header").ToString());
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Assert.True(string.IsNullOrEmpty(request.Headers["X-Trailer-Header"]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
requestsReceived++;
|
requestsReceived++;
|
||||||
|
|
|
||||||
|
|
@ -1174,7 +1174,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(true)]
|
[InlineData(true)]
|
||||||
[InlineData(false)]
|
[InlineData(false)]
|
||||||
public async Task HEADERS_Received_WithTrailers_Discarded(bool sendData)
|
public async Task HEADERS_Received_WithTrailers_Available(bool sendData)
|
||||||
{
|
{
|
||||||
await InitializeConnectionAsync(_readTrailersApplication);
|
await InitializeConnectionAsync(_readTrailersApplication);
|
||||||
|
|
||||||
|
|
@ -1206,10 +1206,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
||||||
VerifyDecodedRequestHeaders(_browserRequestHeaders);
|
VerifyDecodedRequestHeaders(_browserRequestHeaders);
|
||||||
|
|
||||||
// Make sure the trailers are missing. https://github.com/aspnet/KestrelHttpServer/issues/2630
|
// Make sure the trailers are in the trailers collection.
|
||||||
foreach (var header in _requestTrailers)
|
foreach (var header in _requestTrailers)
|
||||||
{
|
{
|
||||||
Assert.False(_receivedHeaders.ContainsKey(header.Key));
|
Assert.False(_receivedHeaders.ContainsKey(header.Key));
|
||||||
|
Assert.True(_receivedTrailers.ContainsKey(header.Key));
|
||||||
|
Assert.Equal(header.Value, _receivedTrailers[header.Key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
|
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
|
||||||
|
|
@ -3288,7 +3290,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(true)]
|
[InlineData(true)]
|
||||||
[InlineData(false)]
|
[InlineData(false)]
|
||||||
public async Task CONTINUATION_Received_WithTrailers_Discarded(bool sendData)
|
public async Task CONTINUATION_Received_WithTrailers_Available(bool sendData)
|
||||||
{
|
{
|
||||||
await InitializeConnectionAsync(_readTrailersApplication);
|
await InitializeConnectionAsync(_readTrailersApplication);
|
||||||
|
|
||||||
|
|
@ -3331,9 +3333,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
||||||
VerifyDecodedRequestHeaders(_browserRequestHeaders);
|
VerifyDecodedRequestHeaders(_browserRequestHeaders);
|
||||||
|
|
||||||
// Make sure the trailers are missing. https://github.com/aspnet/KestrelHttpServer/issues/2630
|
// Make sure the trailers are in the trailers collection.
|
||||||
Assert.False(_receivedHeaders.ContainsKey("trailer-1"));
|
Assert.False(_receivedHeaders.ContainsKey("trailer-1"));
|
||||||
Assert.False(_receivedHeaders.ContainsKey("trailer-2"));
|
Assert.False(_receivedHeaders.ContainsKey("trailer-2"));
|
||||||
|
Assert.True(_receivedTrailers.ContainsKey("trailer-1"));
|
||||||
|
Assert.True(_receivedTrailers.ContainsKey("trailer-2"));
|
||||||
|
Assert.Equal("1", _receivedTrailers["trailer-1"]);
|
||||||
|
Assert.Equal("2", _receivedTrailers["trailer-2"]);
|
||||||
|
|
||||||
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
|
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
||||||
protected readonly ConcurrentDictionary<int, TaskCompletionSource<object>> _runningStreams = new ConcurrentDictionary<int, TaskCompletionSource<object>>();
|
protected readonly ConcurrentDictionary<int, TaskCompletionSource<object>> _runningStreams = new ConcurrentDictionary<int, TaskCompletionSource<object>>();
|
||||||
protected readonly Dictionary<string, string> _receivedHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
protected readonly Dictionary<string, string> _receivedHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
protected readonly Dictionary<string, string> _receivedTrailers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
protected readonly Dictionary<string, string> _decodedHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
protected readonly Dictionary<string, string> _decodedHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
protected readonly HashSet<int> _abortedStreamIds = new HashSet<int>();
|
protected readonly HashSet<int> _abortedStreamIds = new HashSet<int>();
|
||||||
protected readonly object _abortedStreamIdsLock = new object();
|
protected readonly object _abortedStreamIdsLock = new object();
|
||||||
|
|
@ -199,16 +200,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
||||||
_readTrailersApplication = async context =>
|
_readTrailersApplication = async context =>
|
||||||
{
|
{
|
||||||
|
Assert.True(context.Request.SupportsTrailers(), "SupportsTrailers");
|
||||||
|
Assert.False(context.Request.CheckTrailersAvailable(), "SupportsTrailers");
|
||||||
|
|
||||||
using (var ms = new MemoryStream())
|
using (var ms = new MemoryStream())
|
||||||
{
|
{
|
||||||
// Consuming the entire request body guarantees trailers will be available
|
// Consuming the entire request body guarantees trailers will be available
|
||||||
await context.Request.Body.CopyToAsync(ms);
|
await context.Request.Body.CopyToAsync(ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Assert.True(context.Request.SupportsTrailers(), "SupportsTrailers");
|
||||||
|
Assert.True(context.Request.CheckTrailersAvailable(), "SupportsTrailers");
|
||||||
|
|
||||||
foreach (var header in context.Request.Headers)
|
foreach (var header in context.Request.Headers)
|
||||||
{
|
{
|
||||||
_receivedHeaders[header.Key] = header.Value.ToString();
|
_receivedHeaders[header.Key] = header.Value.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var trailers = context.Features.Get<IHttpRequestTrailersFeature>().Trailers;
|
||||||
|
|
||||||
|
foreach (var header in trailers)
|
||||||
|
{
|
||||||
|
_receivedTrailers[header.Key] = header.Value.ToString();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_bufferingApplication = async context =>
|
_bufferingApplication = async context =>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
|
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
|
||||||
|
|
@ -360,7 +361,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
} while (count != 0);
|
} while (count != 0);
|
||||||
|
|
||||||
Assert.Equal("Hello World", Encoding.ASCII.GetString(buffer));
|
Assert.Equal("Hello World", Encoding.ASCII.GetString(buffer));
|
||||||
Assert.Equal("trailing-value", context.Request.Headers["Trailing-Header"].ToString());
|
Assert.Equal("trailing-value", context.Request.GetTrailer("Trailing-Header").ToString());
|
||||||
},
|
},
|
||||||
new TestServiceContext(LoggerFactory) { ServerOptions = { Limits = { MaxRequestBodySize = globalMaxRequestBodySize } } }))
|
new TestServiceContext(LoggerFactory) { ServerOptions = { Limits = { MaxRequestBodySize = globalMaxRequestBodySize } } }))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ namespace CodeGenerator
|
||||||
var commonFeatures = new[]
|
var commonFeatures = new[]
|
||||||
{
|
{
|
||||||
"IHttpAuthenticationFeature",
|
"IHttpAuthenticationFeature",
|
||||||
|
"IHttpRequestTrailersFeature",
|
||||||
"IQueryFeature",
|
"IQueryFeature",
|
||||||
"IFormFeature",
|
"IFormFeature",
|
||||||
};
|
};
|
||||||
|
|
@ -69,6 +70,7 @@ namespace CodeGenerator
|
||||||
"IHttpUpgradeFeature",
|
"IHttpUpgradeFeature",
|
||||||
"IHttpRequestIdentifierFeature",
|
"IHttpRequestIdentifierFeature",
|
||||||
"IHttpRequestLifetimeFeature",
|
"IHttpRequestLifetimeFeature",
|
||||||
|
"IHttpRequestTrailersFeature",
|
||||||
"IHttpConnectionFeature",
|
"IHttpConnectionFeature",
|
||||||
"IHttpMaxRequestBodySizeFeature",
|
"IHttpMaxRequestBodySizeFeature",
|
||||||
"IHttpMinRequestBodyDataRateFeature",
|
"IHttpMinRequestBodyDataRateFeature",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue