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 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 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; }
|
||||
void Abort();
|
||||
}
|
||||
public partial interface IHttpRequestTrailersFeature
|
||||
{
|
||||
bool Available { get; }
|
||||
Microsoft.AspNetCore.Http.IHeaderDictionary Trailers { get; }
|
||||
}
|
||||
public partial interface IHttpResponseFeature
|
||||
{
|
||||
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 GetAsciiStringEscaped(this System.Span<byte> span, int maxChars) { 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 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; }
|
||||
|
|
|
|||
|
|
@ -602,4 +602,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
|
|||
<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>
|
||||
</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>
|
||||
|
|
@ -35,7 +35,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
: base(context)
|
||||
{
|
||||
RequestKeepAlive = keepAlive;
|
||||
|
||||
_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.
|
||||
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;
|
||||
}
|
||||
|
|
@ -489,6 +488,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
consumed = trailerBuffer.End;
|
||||
AddAndCheckConsumedBytes(2);
|
||||
_mode = Mode.Complete;
|
||||
// No trailers
|
||||
_context.OnTrailersComplete();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
break;
|
||||
}
|
||||
case RequestProcessingStatus.ParsingHeaders:
|
||||
if (TakeMessageHeaders(buffer, out consumed, out examined))
|
||||
if (TakeMessageHeaders(buffer, trailers: false, out consumed, out examined))
|
||||
{
|
||||
_requestProcessingStatus = RequestProcessingStatus.AppStarted;
|
||||
}
|
||||
|
|
@ -189,7 +189,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
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
|
||||
bool overLength = false;
|
||||
|
|
@ -202,7 +202,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
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;
|
||||
|
||||
if (!result && overLength)
|
||||
|
|
|
|||
|
|
@ -212,6 +212,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
_context.Input.AdvanceTo(consumed);
|
||||
_finalAdvanceCalled = true;
|
||||
_context.OnTrailersComplete();
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
BadHttpRequestException.Throw(RequestRejectionReason.UpgradeRequestCannotHavePayload);
|
||||
}
|
||||
|
||||
context.OnTrailersComplete(); // No trailers for these.
|
||||
return new Http1UpgradeMessageBody(context);
|
||||
}
|
||||
|
||||
|
|
@ -173,6 +174,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
BadHttpRequestException.Throw(requestRejectionReason, context.Method);
|
||||
}
|
||||
|
||||
context.OnTrailersComplete(); // No trailers for these.
|
||||
return keepAlive ? MessageBody.ZeroContentLengthKeepAlive : MessageBody.ZeroContentLengthClose;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,17 +8,43 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
internal readonly struct Http1ParsingHandler : IHttpRequestLineHandler, IHttpHeadersHandler
|
||||
{
|
||||
public readonly Http1Connection Connection;
|
||||
public readonly bool Trailers;
|
||||
|
||||
public Http1ParsingHandler(Http1Connection connection)
|
||||
{
|
||||
Connection = connection;
|
||||
Trailers = false;
|
||||
}
|
||||
|
||||
public Http1ParsingHandler(Http1Connection connection, bool trailers)
|
||||
{
|
||||
Connection = connection;
|
||||
Trailers = trailers;
|
||||
}
|
||||
|
||||
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()
|
||||
=> 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)
|
||||
=> Connection.OnStartLine(method, version, target, path, query, customMethod, pathEncoded);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
IHttpConnectionFeature,
|
||||
IHttpRequestLifetimeFeature,
|
||||
IHttpRequestIdentifierFeature,
|
||||
IHttpRequestTrailersFeature,
|
||||
IHttpBodyControlFeature,
|
||||
IHttpMaxRequestBodySizeFeature,
|
||||
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
|
||||
{
|
||||
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 IEndpointFeatureType = typeof(IEndpointFeature);
|
||||
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 IFormFeatureType = typeof(IFormFeature);
|
||||
private static readonly Type IHttpUpgradeFeatureType = typeof(IHttpUpgradeFeature);
|
||||
|
|
@ -52,6 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
private object _currentIRouteValuesFeature;
|
||||
private object _currentIEndpointFeature;
|
||||
private object _currentIHttpAuthenticationFeature;
|
||||
private object _currentIHttpRequestTrailersFeature;
|
||||
private object _currentIQueryFeature;
|
||||
private object _currentIFormFeature;
|
||||
private object _currentIHttpUpgradeFeature;
|
||||
|
|
@ -82,6 +84,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
_currentIHttpUpgradeFeature = this;
|
||||
_currentIHttpRequestIdentifierFeature = this;
|
||||
_currentIHttpRequestLifetimeFeature = this;
|
||||
_currentIHttpRequestTrailersFeature = this;
|
||||
_currentIHttpConnectionFeature = this;
|
||||
_currentIHttpMaxRequestBodySizeFeature = this;
|
||||
_currentIHttpMinRequestBodyDataRateFeature = this;
|
||||
|
|
@ -201,6 +204,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
feature = _currentIHttpAuthenticationFeature;
|
||||
}
|
||||
else if (key == IHttpRequestTrailersFeatureType)
|
||||
{
|
||||
feature = _currentIHttpRequestTrailersFeature;
|
||||
}
|
||||
else if (key == IQueryFeatureType)
|
||||
{
|
||||
feature = _currentIQueryFeature;
|
||||
|
|
@ -321,6 +328,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
_currentIHttpAuthenticationFeature = value;
|
||||
}
|
||||
else if (key == IHttpRequestTrailersFeatureType)
|
||||
{
|
||||
_currentIHttpRequestTrailersFeature = value;
|
||||
}
|
||||
else if (key == IQueryFeatureType)
|
||||
{
|
||||
_currentIQueryFeature = value;
|
||||
|
|
@ -439,6 +450,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
feature = (TFeature)_currentIHttpAuthenticationFeature;
|
||||
}
|
||||
else if (typeof(TFeature) == typeof(IHttpRequestTrailersFeature))
|
||||
{
|
||||
feature = (TFeature)_currentIHttpRequestTrailersFeature;
|
||||
}
|
||||
else if (typeof(TFeature) == typeof(IQueryFeature))
|
||||
{
|
||||
feature = (TFeature)_currentIQueryFeature;
|
||||
|
|
@ -563,6 +578,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
_currentIHttpAuthenticationFeature = feature;
|
||||
}
|
||||
else if (typeof(TFeature) == typeof(IHttpRequestTrailersFeature))
|
||||
{
|
||||
_currentIHttpRequestTrailersFeature = feature;
|
||||
}
|
||||
else if (typeof(TFeature) == typeof(IQueryFeature))
|
||||
{
|
||||
_currentIQueryFeature = feature;
|
||||
|
|
@ -679,6 +698,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
yield return new KeyValuePair<Type, object>(IHttpAuthenticationFeatureType, _currentIHttpAuthenticationFeature);
|
||||
}
|
||||
if (_currentIHttpRequestTrailersFeature != null)
|
||||
{
|
||||
yield return new KeyValuePair<Type, object>(IHttpRequestTrailersFeatureType, _currentIHttpRequestTrailersFeature);
|
||||
}
|
||||
if (_currentIQueryFeature != null)
|
||||
{
|
||||
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 RequestTrailers { get; } = new HeaderDictionary();
|
||||
public bool RequestTrailersAvailable { get; set; }
|
||||
public Stream RequestBody { get; set; }
|
||||
public PipeReader RequestBodyPipeReader { get; set; }
|
||||
|
||||
|
|
@ -369,6 +371,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
HttpResponseHeaders.Reset();
|
||||
RequestHeaders = HttpRequestHeaders;
|
||||
ResponseHeaders = HttpResponseHeaders;
|
||||
RequestTrailers.Clear();
|
||||
RequestTrailersAvailable = false;
|
||||
|
||||
_isLeasedMemoryInvalid = true;
|
||||
_hasAdvanced = false;
|
||||
|
|
@ -524,11 +528,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
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()
|
||||
{
|
||||
HttpRequestHeaders.OnHeadersComplete();
|
||||
}
|
||||
|
||||
public void OnTrailersComplete()
|
||||
{
|
||||
RequestTrailersAvailable = true;
|
||||
}
|
||||
|
||||
public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application)
|
||||
{
|
||||
try
|
||||
|
|
|
|||
|
|
@ -103,16 +103,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private unsafe void AppendUnknownHeaders(Span<byte> name, string valueString)
|
||||
{
|
||||
string key = new string('\0', name.Length);
|
||||
fixed (byte* pKeyBytes = name)
|
||||
fixed (char* keyBuffer = key)
|
||||
{
|
||||
if (!StringUtilities.TryGetAsciiString(pKeyBytes, keyBuffer, name.Length))
|
||||
{
|
||||
BadHttpRequestException.Throw(RequestRejectionReason.InvalidCharactersInHeaderName);
|
||||
}
|
||||
}
|
||||
|
||||
string key = name.GetHeaderName();
|
||||
Unknown.TryGetValue(key, out var existing);
|
||||
Unknown[key] = AppendValue(existing, valueString);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1073,9 +1073,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
ValidateHeader(name, value);
|
||||
try
|
||||
{
|
||||
// Drop trailers for now. Adding them to the request headers is not thread safe.
|
||||
// https://github.com/aspnet/KestrelHttpServer/issues/2051
|
||||
if (_requestHeaderParsingState != RequestHeaderParsingState.Trailers)
|
||||
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
|
||||
{
|
||||
_currentHeadersStream.OnTrailer(name, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Throws BadRequest for header count limit breaches.
|
||||
// Throws InvalidOperation for bad encoding.
|
||||
|
|
|
|||
|
|
@ -403,6 +403,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
}
|
||||
}
|
||||
|
||||
OnTrailersComplete();
|
||||
RequestBodyPipe.Writer.Complete();
|
||||
|
||||
_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)
|
||||
{
|
||||
if (span.IsEmpty)
|
||||
|
|
|
|||
|
|
@ -2268,6 +2268,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
internal static string FormatHttp2MinDataRateNotSupported()
|
||||
=> 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)
|
||||
{
|
||||
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"));
|
||||
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);
|
||||
|
||||
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"));
|
||||
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]
|
||||
|
|
@ -129,7 +129,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine}\r\n"));
|
||||
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);
|
||||
|
||||
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"));
|
||||
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);
|
||||
|
||||
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"));
|
||||
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);
|
||||
|
||||
Assert.True(takeMessageHeaders);
|
||||
|
|
@ -262,7 +262,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine2}\r\n"));
|
||||
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);
|
||||
|
||||
Assert.True(takeMessageHeaders);
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_collection[typeof(IRequestBodyPipeFeature)] = CreateHttp1Connection();
|
||||
_collection[typeof(IHttpRequestIdentifierFeature)] = CreateHttp1Connection();
|
||||
_collection[typeof(IHttpRequestLifetimeFeature)] = CreateHttp1Connection();
|
||||
_collection[typeof(IHttpRequestTrailersFeature)] = CreateHttp1Connection();
|
||||
_collection[typeof(IHttpConnectionFeature)] = CreateHttp1Connection();
|
||||
_collection[typeof(IHttpMaxRequestBodySizeFeature)] = CreateHttp1Connection();
|
||||
_collection[typeof(IHttpMinRequestBodyDataRateFeature)] = CreateHttp1Connection();
|
||||
|
|
@ -144,6 +145,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_collection.Set<IRequestBodyPipeFeature>(CreateHttp1Connection());
|
||||
_collection.Set<IHttpRequestIdentifierFeature>(CreateHttp1Connection());
|
||||
_collection.Set<IHttpRequestLifetimeFeature>(CreateHttp1Connection());
|
||||
_collection.Set<IHttpRequestTrailersFeature>(CreateHttp1Connection());
|
||||
_collection.Set<IHttpConnectionFeature>(CreateHttp1Connection());
|
||||
_collection.Set<IHttpMaxRequestBodySizeFeature>(CreateHttp1Connection());
|
||||
_collection.Set<IHttpMinRequestBodyDataRateFeature>(CreateHttp1Connection());
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
ErrorUtilities.ThrowInvalidRequestLine();
|
||||
}
|
||||
|
||||
if (!_http1Connection.TakeMessageHeaders(_buffer, out consumed, out examined))
|
||||
if (!_http1Connection.TakeMessageHeaders(_buffer, trailers: false, out consumed, out examined))
|
||||
{
|
||||
ErrorUtilities.ThrowInvalidRequestHeaders();
|
||||
}
|
||||
|
|
@ -103,7 +103,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
{
|
||||
_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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
|
||||
readableBuffer = readableBuffer.Slice(consumed);
|
||||
|
||||
if (!Http1Connection.TakeMessageHeaders(readableBuffer, out consumed, out examined))
|
||||
if (!Http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out consumed, out examined))
|
||||
{
|
||||
ErrorUtilities.ThrowInvalidRequestHeaders();
|
||||
}
|
||||
|
|
@ -196,7 +196,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
result = Pipe.Reader.ReadAsync().GetAwaiter().GetResult();
|
||||
readableBuffer = result.Buffer;
|
||||
|
||||
if (!Http1Connection.TakeMessageHeaders(readableBuffer, out consumed, out examined))
|
||||
if (!Http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out consumed, out examined))
|
||||
{
|
||||
ErrorUtilities.ThrowInvalidRequestHeaders();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -232,18 +232,59 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
|
||||
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)
|
||||
{
|
||||
;// 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
|
||||
{
|
||||
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++;
|
||||
|
|
@ -278,6 +319,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
"POST / HTTP/1.1",
|
||||
"Host:",
|
||||
"Transfer-Encoding: chunked",
|
||||
"Trailer: X-Trailer-Header",
|
||||
"",
|
||||
"C",
|
||||
$"HelloChunk{i:00}",
|
||||
|
|
@ -315,6 +357,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
var response = httpContext.Response;
|
||||
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)
|
||||
{
|
||||
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
|
||||
{
|
||||
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++;
|
||||
|
|
@ -366,6 +449,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
"POST / HTTP/1.1",
|
||||
"Host:",
|
||||
"Transfer-Encoding: chunked",
|
||||
"Trailer: X-Trailer-Header",
|
||||
"",
|
||||
"C",
|
||||
$"HelloChunk{i:00}",
|
||||
|
|
@ -495,13 +579,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
;// read to end
|
||||
}
|
||||
|
||||
Assert.True(string.IsNullOrEmpty(request.Headers["X-Trailer-Header"]));
|
||||
|
||||
if (requestsReceived < requestCount)
|
||||
{
|
||||
Assert.Equal(new string('a', requestsReceived), request.Headers["X-Trailer-Header"].ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.True(string.IsNullOrEmpty(request.Headers["X-Trailer-Header"]));
|
||||
Assert.Equal(new string('a', requestsReceived), request.GetTrailer("X-Trailer-Header").ToString());
|
||||
}
|
||||
|
||||
requestsReceived++;
|
||||
|
|
|
|||
|
|
@ -1174,7 +1174,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task HEADERS_Received_WithTrailers_Discarded(bool sendData)
|
||||
public async Task HEADERS_Received_WithTrailers_Available(bool sendData)
|
||||
{
|
||||
await InitializeConnectionAsync(_readTrailersApplication);
|
||||
|
||||
|
|
@ -1206,10 +1206,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
|
@ -3288,7 +3290,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task CONTINUATION_Received_WithTrailers_Discarded(bool sendData)
|
||||
public async Task CONTINUATION_Received_WithTrailers_Available(bool sendData)
|
||||
{
|
||||
await InitializeConnectionAsync(_readTrailersApplication);
|
||||
|
||||
|
|
@ -3331,9 +3333,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
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-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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 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 HashSet<int> _abortedStreamIds = new HashSet<int>();
|
||||
protected readonly object _abortedStreamIdsLock = new object();
|
||||
|
|
@ -199,16 +200,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
_readTrailersApplication = async context =>
|
||||
{
|
||||
Assert.True(context.Request.SupportsTrailers(), "SupportsTrailers");
|
||||
Assert.False(context.Request.CheckTrailersAvailable(), "SupportsTrailers");
|
||||
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
// Consuming the entire request body guarantees trailers will be available
|
||||
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)
|
||||
{
|
||||
_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 =>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
|
||||
|
|
@ -360,7 +361,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
} while (count != 0);
|
||||
|
||||
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 } } }))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ namespace CodeGenerator
|
|||
var commonFeatures = new[]
|
||||
{
|
||||
"IHttpAuthenticationFeature",
|
||||
"IHttpRequestTrailersFeature",
|
||||
"IQueryFeature",
|
||||
"IFormFeature",
|
||||
};
|
||||
|
|
@ -69,6 +70,7 @@ namespace CodeGenerator
|
|||
"IHttpUpgradeFeature",
|
||||
"IHttpRequestIdentifierFeature",
|
||||
"IHttpRequestLifetimeFeature",
|
||||
"IHttpRequestTrailersFeature",
|
||||
"IHttpConnectionFeature",
|
||||
"IHttpMaxRequestBodySizeFeature",
|
||||
"IHttpMinRequestBodyDataRateFeature",
|
||||
|
|
|
|||
Loading…
Reference in New Issue