Make HTTP/1.1 startline parsing "safe" (#20885)

This commit is contained in:
Ben Adams 2020-04-24 02:32:54 +01:00 committed by GitHub
parent 351359f56d
commit f5fa16e998
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 542 additions and 411 deletions

View File

@ -227,7 +227,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public HttpParser() { } public HttpParser() { }
public HttpParser(bool showErrorDetails) { } public HttpParser(bool showErrorDetails) { }
public bool ParseHeaders(TRequestHandler handler, ref System.Buffers.SequenceReader<byte> reader) { throw null; } public bool ParseHeaders(TRequestHandler handler, ref System.Buffers.SequenceReader<byte> reader) { throw null; }
public bool ParseRequestLine(TRequestHandler handler, in System.Buffers.ReadOnlySequence<byte> buffer, out System.SequencePosition consumed, out System.SequencePosition examined) { throw null; } public bool ParseRequestLine(TRequestHandler handler, ref System.Buffers.SequenceReader<byte> reader) { throw null; }
} }
public enum HttpScheme public enum HttpScheme
{ {
@ -235,13 +235,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
Http = 0, Http = 0,
Https = 1, Https = 1,
} }
public enum HttpVersion public enum HttpVersion : sbyte
{ {
Unknown = -1, Unknown = (sbyte)-1,
Http10 = 0, Http10 = (sbyte)0,
Http11 = 1, Http11 = (sbyte)1,
Http2 = 2, Http2 = (sbyte)2,
Http3 = 3, Http3 = (sbyte)3,
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public partial struct HttpVersionAndMethod
{
private int _dummyPrimitive;
public HttpVersionAndMethod(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod method, int methodEnd) { throw null; }
public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod Method { get { throw null; } }
public int MethodEnd { get { throw null; } }
public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpVersion Version { get { throw null; } set { } }
} }
public partial interface IHttpHeadersHandler public partial interface IHttpHeadersHandler
{ {
@ -252,7 +261,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
public partial interface IHttpRequestLineHandler public partial interface IHttpRequestLineHandler
{ {
void OnStartLine(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod method, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpVersion version, System.Span<byte> target, System.Span<byte> path, System.Span<byte> query, System.Span<byte> customMethod, bool pathEncoded); void OnStartLine(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpVersionAndMethod versionAndMethod, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.TargetOffsetPathLength targetPath, System.Span<byte> startLine);
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct TargetOffsetPathLength
{
private readonly int _dummyPrimitive;
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public TargetOffsetPathLength(int offset, int length, bool isEncoded) { throw null; }
public bool IsEncoded { [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]get { throw null; } }
public int Length { [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]get { throw null; } }
public int Offset { [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]get { throw null; } }
} }
} }
namespace Microsoft.AspNetCore.Server.Kestrel.Https namespace Microsoft.AspNetCore.Server.Kestrel.Https

View File

@ -310,10 +310,18 @@ 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, trailers: true, out consumed, out examined)) var reader = new SequenceReader<byte>(readableBuffer);
if (_context.TakeMessageHeaders(ref reader, trailers: true))
{ {
examined = reader.Position;
_mode = Mode.Complete; _mode = Mode.Complete;
} }
else
{
examined = readableBuffer.End;
}
consumed = reader.Position;
} }
return _mode == Mode.Complete; return _mode == Mode.Complete;

View File

@ -139,15 +139,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
SendTimeoutResponse(); SendTimeoutResponse();
} }
public void ParseRequest(in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined) public bool ParseRequest(ref SequenceReader<byte> reader)
{ {
consumed = buffer.Start;
examined = buffer.End;
switch (_requestProcessingStatus) switch (_requestProcessingStatus)
{ {
case RequestProcessingStatus.RequestPending: case RequestProcessingStatus.RequestPending:
if (buffer.IsEmpty) if (reader.End)
{ {
break; break;
} }
@ -157,75 +154,68 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_requestProcessingStatus = RequestProcessingStatus.ParsingRequestLine; _requestProcessingStatus = RequestProcessingStatus.ParsingRequestLine;
goto case RequestProcessingStatus.ParsingRequestLine; goto case RequestProcessingStatus.ParsingRequestLine;
case RequestProcessingStatus.ParsingRequestLine: case RequestProcessingStatus.ParsingRequestLine:
if (TakeStartLine(buffer, out consumed, out examined)) if (TakeStartLine(ref reader))
{ {
TrimAndParseHeaders(buffer, ref consumed, out examined); _requestProcessingStatus = RequestProcessingStatus.ParsingHeaders;
return; goto case RequestProcessingStatus.ParsingHeaders;
} }
else else
{ {
break; break;
} }
case RequestProcessingStatus.ParsingHeaders: case RequestProcessingStatus.ParsingHeaders:
if (TakeMessageHeaders(buffer, trailers: false, out consumed, out examined)) if (TakeMessageHeaders(ref reader, trailers: false))
{ {
_requestProcessingStatus = RequestProcessingStatus.AppStarted; _requestProcessingStatus = RequestProcessingStatus.AppStarted;
// Consumed preamble
return true;
} }
break; break;
} }
void TrimAndParseHeaders(in ReadOnlySequence<byte> buffer, ref SequencePosition consumed, out SequencePosition examined) // Haven't completed consuming preamble
{ return false;
var trimmedBuffer = buffer.Slice(consumed, buffer.End);
_requestProcessingStatus = RequestProcessingStatus.ParsingHeaders;
if (TakeMessageHeaders(trimmedBuffer, trailers: false, out consumed, out examined))
{
_requestProcessingStatus = RequestProcessingStatus.AppStarted;
}
}
} }
public bool TakeStartLine(in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined) public bool TakeStartLine(ref SequenceReader<byte> reader)
{ {
// Make sure the buffer is limited // Make sure the buffer is limited
if (buffer.Length >= ServerOptions.Limits.MaxRequestLineSize) if (reader.Remaining >= ServerOptions.Limits.MaxRequestLineSize)
{ {
// Input oversize, cap amount checked // Input oversize, cap amount checked
return TrimAndTakeStartLine(buffer, out consumed, out examined); return TrimAndTakeStartLine(ref reader);
} }
return _parser.ParseRequestLine(new Http1ParsingHandler(this), buffer, out consumed, out examined); return _parser.ParseRequestLine(new Http1ParsingHandler(this), ref reader);
bool TrimAndTakeStartLine(in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined) bool TrimAndTakeStartLine(ref SequenceReader<byte> reader)
{ {
var trimmedBuffer = buffer.Slice(buffer.Start, ServerOptions.Limits.MaxRequestLineSize); var trimmedBuffer = reader.Sequence.Slice(reader.Position, ServerOptions.Limits.MaxRequestLineSize);
var trimmedReader = new SequenceReader<byte>(trimmedBuffer);
if (!_parser.ParseRequestLine(new Http1ParsingHandler(this), trimmedBuffer, out consumed, out examined)) if (!_parser.ParseRequestLine(new Http1ParsingHandler(this), ref trimmedReader))
{ {
// We read the maximum allowed but didn't complete the start line. // We read the maximum allowed but didn't complete the start line.
KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestLineTooLong); KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestLineTooLong);
} }
reader.Advance(trimmedReader.Consumed);
return true; return true;
} }
} }
public bool TakeMessageHeaders(in ReadOnlySequence<byte> buffer, bool trailers, out SequencePosition consumed, out SequencePosition examined) public bool TakeMessageHeaders(ref SequenceReader<byte> reader, bool trailers)
{ {
// Make sure the buffer is limited // Make sure the buffer is limited
if (buffer.Length > _remainingRequestHeadersBytesAllowed) if (reader.Remaining > _remainingRequestHeadersBytesAllowed)
{ {
// Input oversize, cap amount checked // Input oversize, cap amount checked
return TrimAndTakeMessageHeaders(buffer, trailers, out consumed, out examined); return TrimAndTakeMessageHeaders(ref reader, trailers);
} }
var reader = new SequenceReader<byte>(buffer);
var result = false;
try try
{ {
result = _parser.ParseHeaders(new Http1ParsingHandler(this, trailers), ref reader); var result = _parser.ParseHeaders(new Http1ParsingHandler(this, trailers), ref reader);
if (result) if (result)
{ {
TimeoutControl.CancelTimeout(); TimeoutControl.CancelTimeout();
@ -235,30 +225,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
finally finally
{ {
consumed = reader.Position;
_remainingRequestHeadersBytesAllowed -= (int)reader.Consumed; _remainingRequestHeadersBytesAllowed -= (int)reader.Consumed;
if (result)
{
examined = consumed;
}
else
{
examined = buffer.End;
}
} }
bool TrimAndTakeMessageHeaders(in ReadOnlySequence<byte> buffer, bool trailers, out SequencePosition consumed, out SequencePosition examined) bool TrimAndTakeMessageHeaders(ref SequenceReader<byte> reader, bool trailers)
{ {
var trimmedBuffer = buffer.Slice(buffer.Start, _remainingRequestHeadersBytesAllowed); var trimmedBuffer = reader.Sequence.Slice(reader.Position, _remainingRequestHeadersBytesAllowed);
var trimmedReader = new SequenceReader<byte>(trimmedBuffer);
var reader = new SequenceReader<byte>(trimmedBuffer);
var result = false;
try try
{ {
result = _parser.ParseHeaders(new Http1ParsingHandler(this, trailers), ref reader); if (!_parser.ParseHeaders(new Http1ParsingHandler(this, trailers), ref trimmedReader))
if (!result)
{ {
// We read the maximum allowed but didn't complete the headers. // We read the maximum allowed but didn't complete the headers.
KestrelBadHttpRequestException.Throw(RequestRejectionReason.HeadersExceedMaxTotalSize); KestrelBadHttpRequestException.Throw(RequestRejectionReason.HeadersExceedMaxTotalSize);
@ -266,44 +242,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
TimeoutControl.CancelTimeout(); TimeoutControl.CancelTimeout();
return result; reader.Advance(trimmedReader.Consumed);
return true;
} }
finally finally
{ {
consumed = reader.Position;
_remainingRequestHeadersBytesAllowed -= (int)reader.Consumed; _remainingRequestHeadersBytesAllowed -= (int)reader.Consumed;
if (result)
{
examined = consumed;
}
else
{
examined = trimmedBuffer.End;
}
} }
} }
} }
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded) public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
{ {
var targetStart = targetPath.Offset;
// Slice out target
var target = startLine[targetStart..];
Debug.Assert(target.Length != 0, "Request target must be non-zero length"); Debug.Assert(target.Length != 0, "Request target must be non-zero length");
var method = versionAndMethod.Method;
var ch = target[0]; var ch = target[0];
if (ch == ByteForwardSlash) if (ch == ByteForwardSlash)
{ {
// origin-form. // origin-form.
// The most common form of request-target. // The most common form of request-target.
// https://tools.ietf.org/html/rfc7230#section-5.3.1 // https://tools.ietf.org/html/rfc7230#section-5.3.1
OnOriginFormTarget(pathEncoded, target, path, query); OnOriginFormTarget(targetPath, target);
} }
else if (ch == ByteAsterisk && target.Length == 1) else if (ch == ByteAsterisk && target.Length == 1)
{ {
OnAsteriskFormTarget(method); OnAsteriskFormTarget(method);
} }
else if (target.GetKnownHttpScheme(out _)) else if (startLine[targetStart..].GetKnownHttpScheme(out _))
{ {
OnAbsoluteFormTarget(target, query); OnAbsoluteFormTarget(targetPath, target);
} }
else else
{ {
@ -316,10 +287,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
Method = method; Method = method;
if (method == HttpMethod.Custom) if (method == HttpMethod.Custom)
{ {
_methodText = customMethod.GetAsciiStringNonNullCharacters(); _methodText = startLine[..versionAndMethod.MethodEnd].GetAsciiStringNonNullCharacters();
} }
_httpVersion = version; _httpVersion = versionAndMethod.Version;
Debug.Assert(RawTarget != null, "RawTarget was not set"); Debug.Assert(RawTarget != null, "RawTarget was not set");
Debug.Assert(((IHttpRequestFeature)this).Method != null, "Method was not set"); Debug.Assert(((IHttpRequestFeature)this).Method != null, "Method was not set");
@ -329,7 +300,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
// Compare with Http2Stream.TryValidatePseudoHeaders // Compare with Http2Stream.TryValidatePseudoHeaders
private void OnOriginFormTarget(bool pathEncoded, Span<byte> target, Span<byte> path, Span<byte> query) private void OnOriginFormTarget(TargetOffsetPathLength targetPath, Span<byte> target)
{ {
Debug.Assert(target[0] == ByteForwardSlash, "Should only be called when path starts with /"); Debug.Assert(target[0] == ByteForwardSlash, "Should only be called when path starts with /");
@ -349,59 +320,69 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
return; return;
} }
// Read raw target before mutating memory.
var previousValue = _parsedRawTarget;
if (ServerOptions.DisableStringReuse ||
previousValue == null || previousValue.Length != target.Length ||
!StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, target))
{
ParseTarget(targetPath, target);
}
else
{
// As RawTarget is the same we can reuse the previous parsed values.
RawTarget = previousValue;
Path = _parsedPath;
QueryString = _parsedQueryString;
}
// Clear parsedData for absolute target as we won't check it if we come via this path again,
// an setting to null is fast as it doesn't need to use a GC write barrier.
_parsedAbsoluteRequestTarget = null;
}
private void ParseTarget(TargetOffsetPathLength targetPath, Span<byte> target)
{
// URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11 // URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11
// Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8; // Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8;
// then encoded/escaped to ASCII https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs" // then encoded/escaped to ASCII https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs"
try try
{ {
var disableStringReuse = ServerOptions.DisableStringReuse; // The previous string does not match what the bytes would convert to,
// Read raw target before mutating memory. // so we will need to generate a new string.
var previousValue = _parsedRawTarget; RawTarget = _parsedRawTarget = target.GetAsciiStringNonNullCharacters();
if (disableStringReuse ||
previousValue == null || previousValue.Length != target.Length ||
!StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, target))
{
// The previous string does not match what the bytes would convert to,
// so we will need to generate a new string.
RawTarget = _parsedRawTarget = target.GetAsciiStringNonNullCharacters();
previousValue = _parsedQueryString; var queryLength = 0;
if (disableStringReuse || if (target.Length == targetPath.Length)
previousValue == null || previousValue.Length != query.Length || {
!StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, query)) // No query string
if (ReferenceEquals(_parsedQueryString, string.Empty))
{ {
// The previous string does not match what the bytes would convert to,
// so we will need to generate a new string.
QueryString = _parsedQueryString = query.GetAsciiStringNonNullCharacters();
}
else
{
// Same as previous
QueryString = _parsedQueryString; QueryString = _parsedQueryString;
} }
if (path.Length == 1)
{
// If path.Length == 1 it can only be a forward slash (e.g. home page)
Path = _parsedPath = ForwardSlash;
}
else else
{ {
Path = _parsedPath = PathNormalizer.DecodePath(path, pathEncoded, RawTarget, query.Length); QueryString = string.Empty;
_parsedQueryString = string.Empty;
} }
} }
else else
{ {
// As RawTarget is the same we can reuse the previous parsed values. queryLength = ParseQuery(targetPath, target);
RawTarget = _parsedRawTarget;
Path = _parsedPath;
QueryString = _parsedQueryString;
} }
// Clear parsedData for absolute target as we won't check it if we come via this path again, var pathLength = targetPath.Length;
// an setting to null is fast as it doesn't need to use a GC write barrier. if (pathLength == 1)
_parsedAbsoluteRequestTarget = null; {
// If path.Length == 1 it can only be a forward slash (e.g. home page)
Path = _parsedPath = ForwardSlash;
}
else
{
var path = target[..pathLength];
Path = _parsedPath = PathNormalizer.DecodePath(path, targetPath.IsEncoded, RawTarget, queryLength);
}
} }
catch (InvalidOperationException) catch (InvalidOperationException)
{ {
@ -409,6 +390,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
} }
private int ParseQuery(TargetOffsetPathLength targetPath, Span<byte> target)
{
var previousValue = _parsedQueryString;
var query = target[targetPath.Length..];
var queryLength = query.Length;
if (ServerOptions.DisableStringReuse ||
previousValue == null || previousValue.Length != queryLength ||
!StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, query))
{
// The previous string does not match what the bytes would convert to,
// so we will need to generate a new string.
QueryString = _parsedQueryString = query.GetAsciiStringNonNullCharacters();
}
else
{
// Same as previous
QueryString = _parsedQueryString;
}
return queryLength;
}
private void OnAuthorityFormTarget(HttpMethod method, Span<byte> target) private void OnAuthorityFormTarget(HttpMethod method, Span<byte> target)
{ {
_requestTargetForm = HttpRequestTarget.AuthorityForm; _requestTargetForm = HttpRequestTarget.AuthorityForm;
@ -480,8 +483,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_parsedAbsoluteRequestTarget = null; _parsedAbsoluteRequestTarget = null;
} }
private void OnAbsoluteFormTarget(Span<byte> target, Span<byte> query) private void OnAbsoluteFormTarget(TargetOffsetPathLength targetPath, Span<byte> target)
{ {
Span<byte> query = target[targetPath.Length..];
_requestTargetForm = HttpRequestTarget.AbsoluteForm; _requestTargetForm = HttpRequestTarget.AbsoluteForm;
// absolute-form // absolute-form
@ -645,12 +649,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
protected override bool TryParseRequest(ReadResult result, out bool endConnection) protected override bool TryParseRequest(ReadResult result, out bool endConnection)
{ {
var examined = result.Buffer.End; var reader = new SequenceReader<byte>(result.Buffer);
var consumed = result.Buffer.End; var isConsumed = false;
try try
{ {
ParseRequest(result.Buffer, out consumed, out examined); isConsumed = ParseRequest(ref reader);
} }
catch (InvalidOperationException) catch (InvalidOperationException)
{ {
@ -662,7 +665,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
finally finally
{ {
Input.AdvanceTo(consumed, examined); Input.AdvanceTo(reader.Position, isConsumed ? reader.Position : result.Buffer.End);
} }
if (result.IsCompleted) if (result.IsCompleted)

View File

@ -47,8 +47,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
} }
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded) public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
=> Connection.OnStartLine(method, version, target, path, query, customMethod, pathEncoded); => Connection.OnStartLine(versionAndMethod, targetPath, startLine);
public void OnStaticIndexedHeader(int index) public void OnStaticIndexedHeader(int index)
{ {

View File

@ -5,6 +5,7 @@ using System;
using System.Buffers; using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
@ -34,84 +35,55 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private const byte BytePercentage = (byte)'%'; private const byte BytePercentage = (byte)'%';
private const int MinTlsRequestSize = 1; // We need at least 1 byte to check for a proper TLS request line private const int MinTlsRequestSize = 1; // We need at least 1 byte to check for a proper TLS request line
public unsafe bool ParseRequestLine(TRequestHandler handler, in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined) public bool ParseRequestLine(TRequestHandler handler, ref SequenceReader<byte> reader)
{ {
consumed = buffer.Start; if (reader.TryReadTo(out ReadOnlySpan<byte> requestLine, ByteLF, advancePastDelimiter: true))
examined = buffer.End;
// Prepare the first span
var span = buffer.FirstSpan;
var lineIndex = span.IndexOf(ByteLF);
if (lineIndex >= 0)
{ {
consumed = buffer.GetPosition(lineIndex + 1, consumed); ParseRequestLine(handler, requestLine);
span = span.Slice(0, lineIndex + 1); return true;
}
else if (buffer.IsSingleSegment)
{
// No request line end
return false;
}
else if (TryGetNewLine(buffer, out var found))
{
span = buffer.Slice(consumed, found).ToSpan();
consumed = found;
}
else
{
// No request line end
return false;
} }
// Fix and parse the span return false;
fixed (byte* data = span)
{
ParseRequestLine(handler, data, span.Length);
}
examined = consumed;
return true;
} }
private unsafe void ParseRequestLine(TRequestHandler handler, byte* data, int length) private void ParseRequestLine(TRequestHandler handler, ReadOnlySpan<byte> requestLine)
{ {
// Get Method and set the offset // Get Method and set the offset
var method = HttpUtilities.GetKnownMethod(data, length, out var pathStartOffset); var method = requestLine.GetKnownMethod(out var methodEnd);
Span<byte> customMethod = default;
if (method == HttpMethod.Custom) if (method == HttpMethod.Custom)
{ {
customMethod = GetUnknownMethod(data, length, out pathStartOffset); methodEnd = GetUnknownMethodLength(requestLine);
} }
// Use a new offset var as pathStartOffset needs to be on stack var versionAndMethod = new HttpVersionAndMethod(method, methodEnd);
// Use a new offset var as methodEnd needs to be on stack
// as its passed by reference above so can't be in register. // as its passed by reference above so can't be in register.
// Skip space // Skip space
var offset = pathStartOffset + 1; var offset = methodEnd + 1;
if (offset >= length) if ((uint)offset >= (uint)requestLine.Length)
{ {
// Start of path not found // Start of path not found
RejectRequestLine(data, length); RejectRequestLine(requestLine);
} }
byte ch = data[offset]; var ch = requestLine[offset];
if (ch == ByteSpace || ch == ByteQuestionMark || ch == BytePercentage) if (ch == ByteSpace || ch == ByteQuestionMark || ch == BytePercentage)
{ {
// Empty path is illegal, or path starting with percentage // Empty path is illegal, or path starting with percentage
RejectRequestLine(data, length); RejectRequestLine(requestLine);
} }
// Target = Path and Query // Target = Path and Query
var targetStart = offset;
var pathEncoded = false; var pathEncoded = false;
var pathStart = offset;
// Skip first char (just checked) // Skip first char (just checked)
offset++; offset++;
// Find end of path and if path is encoded // Find end of path and if path is encoded
for (; offset < length; offset++) for (; (uint)offset < (uint)requestLine.Length; offset++)
{ {
ch = data[offset]; ch = requestLine[offset];
if (ch == ByteSpace || ch == ByteQuestionMark) if (ch == ByteSpace || ch == ByteQuestionMark)
{ {
// End of path // End of path
@ -123,16 +95,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
} }
var pathBuffer = new Span<byte>(data + pathStart, offset - pathStart); var path = new TargetOffsetPathLength(targetStart, length: offset - targetStart, pathEncoded);
// Query string // Query string
var queryStart = offset;
if (ch == ByteQuestionMark) if (ch == ByteQuestionMark)
{ {
// We have a query string // We have a query string
for (; offset < length; offset++) for (; (uint)offset < (uint)requestLine.Length; offset++)
{ {
ch = data[offset]; ch = requestLine[offset];
if (ch == ByteSpace) if (ch == ByteSpace)
{ {
break; break;
@ -140,41 +111,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
} }
// End of query string not found var queryEnd = offset;
if (offset == length)
{
RejectRequestLine(data, length);
}
var targetBuffer = new Span<byte>(data + pathStart, offset - pathStart);
var query = new Span<byte>(data + queryStart, offset - queryStart);
// Consume space // Consume space
offset++; offset++;
// If offset has overshot length, end of query string wasn't not found
if ((uint)offset > (uint)requestLine.Length)
{
RejectRequestLine(requestLine);
}
// Version // Version
var httpVersion = HttpUtilities.GetKnownVersion(data + offset, length - offset); var remaining = requestLine.Slice(offset);
var httpVersion = remaining.GetKnownVersionAndConfirmCR();
versionAndMethod.Version = httpVersion;
if (httpVersion == HttpVersion.Unknown) if (httpVersion == HttpVersion.Unknown)
{ {
if (data[offset] == ByteCR || data[length - 2] != ByteCR) // HTTP version is unsupported or incorrectly terminated.
{ RejectUnknownVersion(offset, requestLine);
// If missing delimiter or CR before LF, reject and log entire line
RejectRequestLine(data, length);
}
else
{
// else inform HTTP version is unsupported.
RejectUnknownVersion(data + offset, length - offset - 2);
}
} }
// After version's 8 bytes and CR, expect LF // Version + CR is 8 bytes; adding 9 should take us to .Length
if (data[offset + 8 + 1] != ByteLF) offset += 9;
// LF should have been dropped prior to method call, so offset should now be length
if ((uint)offset != (uint)requestLine.Length)
{ {
RejectRequestLine(data, length); RejectRequestLine(requestLine);
} }
handler.OnStartLine(method, httpVersion, targetBuffer, pathBuffer, query, customMethod, pathEncoded); // We need to reinterpret from ReadOnlySpan into Span to allow path mutation for
// in-place normalization and decoding to transform into a canonical path
var startLine = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(requestLine), queryEnd);
handler.OnStartLine(versionAndMethod, path, startLine);
} }
public bool ParseHeaders(TRequestHandler handler, ref SequenceReader<byte> reader) public bool ParseHeaders(TRequestHandler handler, ref SequenceReader<byte> reader)
@ -460,56 +428,36 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private static bool TryGetNewLine(in ReadOnlySequence<byte> buffer, out SequencePosition found) private int GetUnknownMethodLength(ReadOnlySpan<byte> span)
{ {
var byteLfPosition = buffer.PositionOf(ByteLF); var invalidIndex = HttpCharacters.IndexOfInvalidTokenChar(span);
if (byteLfPosition != null)
if (invalidIndex <= 0 || span[invalidIndex] != ByteSpace)
{ {
// Move 1 byte past the \n RejectRequestLine(span);
found = buffer.GetPosition(1, byteLfPosition.Value);
return true;
} }
found = default; return invalidIndex;
return false;
} }
[MethodImpl(MethodImplOptions.NoInlining)] private bool IsTlsHandshake(ReadOnlySpan<byte> requestLine)
private unsafe Span<byte> GetUnknownMethod(byte* data, int length, out int methodLength)
{
var invalidIndex = HttpCharacters.IndexOfInvalidTokenChar(data, length);
if (invalidIndex <= 0 || data[invalidIndex] != ByteSpace)
{
RejectRequestLine(data, length);
}
methodLength = invalidIndex;
return new Span<byte>(data, methodLength);
}
private unsafe bool IsTlsHandshake(byte* data, int length)
{ {
const byte SslRecordTypeHandshake = (byte)0x16; const byte SslRecordTypeHandshake = (byte)0x16;
// Make sure we can check at least for the existence of a TLS handshake - we check the first byte // Make sure we can check at least for the existence of a TLS handshake - we check the first byte
// See https://serializethoughts.com/2014/07/27/dissecting-tls-client-hello-message/ // See https://serializethoughts.com/2014/07/27/dissecting-tls-client-hello-message/
return (length >= MinTlsRequestSize && data[0] == SslRecordTypeHandshake); return (requestLine.Length >= MinTlsRequestSize && requestLine[0] == SslRecordTypeHandshake);
} }
[StackTraceHidden] [StackTraceHidden]
private unsafe void RejectRequestLine(byte* requestLine, int length) private void RejectRequestLine(ReadOnlySpan<byte> requestLine)
{ {
// Check for incoming TLS handshake over HTTP throw GetInvalidRequestException(
if (IsTlsHandshake(requestLine, length)) IsTlsHandshake(requestLine) ?
{ RequestRejectionReason.TlsOverHttpError :
throw GetInvalidRequestException(RequestRejectionReason.TlsOverHttpError, requestLine, length); RequestRejectionReason.InvalidRequestLine,
} requestLine);
else
{
throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine, length);
}
} }
[StackTraceHidden] [StackTraceHidden]
@ -517,12 +465,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
=> throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestHeader, headerLine); => throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestHeader, headerLine);
[StackTraceHidden] [StackTraceHidden]
private unsafe void RejectUnknownVersion(byte* version, int length) private void RejectUnknownVersion(int offset, ReadOnlySpan<byte> requestLine)
=> throw GetInvalidRequestException(RequestRejectionReason.UnrecognizedHTTPVersion, version, length); // If CR before LF, reject and log entire line
=> throw (((uint)offset >= (uint)requestLine.Length || requestLine[offset] == ByteCR || requestLine[^1] != ByteCR) ?
[MethodImpl(MethodImplOptions.NoInlining)] GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine) :
private unsafe BadHttpRequestException GetInvalidRequestException(RequestRejectionReason reason, byte* detail, int length) GetInvalidRequestException(RequestRejectionReason.UnrecognizedHTTPVersion, requestLine[offset..^1]));
=> GetInvalidRequestException(reason, new ReadOnlySpan<byte>(detail, length));
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private BadHttpRequestException GetInvalidRequestException(RequestRejectionReason reason, ReadOnlySpan<byte> headerLine) private BadHttpRequestException GetInvalidRequestException(RequestRejectionReason reason, ReadOnlySpan<byte> headerLine)

View File

@ -3,7 +3,7 @@
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
public enum HttpVersion public enum HttpVersion : sbyte
{ {
Unknown = -1, Unknown = -1,
Http10 = 0, Http10 = 0,

View File

@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
internal interface IHttpParser<TRequestHandler> where TRequestHandler : IHttpHeadersHandler, IHttpRequestLineHandler internal interface IHttpParser<TRequestHandler> where TRequestHandler : IHttpHeadersHandler, IHttpRequestLineHandler
{ {
bool ParseRequestLine(TRequestHandler handler, in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined); bool ParseRequestLine(TRequestHandler handler, ref SequenceReader<byte> reader);
bool ParseHeaders(TRequestHandler handler, ref SequenceReader<byte> reader); bool ParseHeaders(TRequestHandler handler, ref SequenceReader<byte> reader);
} }

View File

@ -2,11 +2,85 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Runtime.CompilerServices;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
public interface IHttpRequestLineHandler public interface IHttpRequestLineHandler
{ {
void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded); void OnStartLine(
HttpVersionAndMethod versionAndMethod,
TargetOffsetPathLength targetPath,
Span<byte> startLine);
}
public struct HttpVersionAndMethod
{
private ulong _versionAndMethod;
public HttpVersionAndMethod(HttpMethod method, int methodEnd)
{
_versionAndMethod = ((ulong)(uint)methodEnd << 32) | ((ulong)method << 8);
}
public HttpVersion Version
{
get => (HttpVersion)(sbyte)(byte)_versionAndMethod;
set => _versionAndMethod = (_versionAndMethod & ~0xFFul) | (byte)value;
}
public HttpMethod Method => (HttpMethod)(byte)(_versionAndMethod >> 8);
public int MethodEnd => (int)(uint)(_versionAndMethod >> 32);
}
public readonly struct TargetOffsetPathLength
{
private readonly ulong _targetOffsetPathLength;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TargetOffsetPathLength(int offset, int length, bool isEncoded)
{
if (isEncoded)
{
length = -length;
}
_targetOffsetPathLength = ((ulong)offset << 32) | (uint)length;
}
public int Offset
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return (int)(_targetOffsetPathLength >> 32);
}
}
public int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
var length = (int)_targetOffsetPathLength;
if (length < 0)
{
length = -length;
}
return length;
}
}
public bool IsEncoded
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return (int)_targetOffsetPathLength < 0 ? true : false;
}
}
} }
} }

View File

@ -166,13 +166,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe static int IndexOfInvalidTokenChar(byte* s, int length) public static int IndexOfInvalidTokenChar(ReadOnlySpan<byte> span)
{ {
var token = _token; var token = _token;
for (var i = 0; i < length; i++) for (var i = 0; i < span.Length; i++)
{ {
var c = s[i]; var c = span[i];
if (c >= (uint)token.Length || !token[c]) if (c >= (uint)token.Length || !token[c])
{ {
return i; return i;

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Buffers.Binary;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -50,38 +51,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
} }
} }
private static unsafe ulong GetAsciiStringAsLong(string str) private static ulong GetAsciiStringAsLong(string str)
{ {
Debug.Assert(str.Length == 8, "String must be exactly 8 (ASCII) characters long."); Debug.Assert(str.Length == 8, "String must be exactly 8 (ASCII) characters long.");
var bytes = Encoding.ASCII.GetBytes(str); var bytes = Encoding.ASCII.GetBytes(str);
fixed (byte* ptr = &bytes[0]) return BinaryPrimitives.ReadUInt64LittleEndian(bytes);
{
return *(ulong*)ptr;
}
} }
private static unsafe uint GetAsciiStringAsInt(string str) private static uint GetAsciiStringAsInt(string str)
{ {
Debug.Assert(str.Length == 4, "String must be exactly 4 (ASCII) characters long."); Debug.Assert(str.Length == 4, "String must be exactly 4 (ASCII) characters long.");
var bytes = Encoding.ASCII.GetBytes(str); var bytes = Encoding.ASCII.GetBytes(str);
return BinaryPrimitives.ReadUInt32LittleEndian(bytes);
fixed (byte* ptr = &bytes[0])
{
return *(uint*)ptr;
}
} }
private static unsafe ulong GetMaskAsLong(byte[] bytes) private static ulong GetMaskAsLong(byte[] bytes)
{ {
Debug.Assert(bytes.Length == 8, "Mask must be exactly 8 bytes long."); Debug.Assert(bytes.Length == 8, "Mask must be exactly 8 bytes long.");
fixed (byte* ptr = bytes) return BinaryPrimitives.ReadUInt64LittleEndian(bytes);
{
return *(ulong*)ptr;
}
} }
// The same as GetAsciiStringNonNullCharacters but throws BadRequest // The same as GetAsciiStringNonNullCharacters but throws BadRequest
@ -142,7 +133,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
} }
} }
private static unsafe string GetAsciiOrUTF8StringNonNullCharacters(this Span<byte> span) private static string GetAsciiOrUTF8StringNonNullCharacters(this Span<byte> span)
=> GetAsciiOrUTF8StringNonNullCharacters((ReadOnlySpan<byte>)span); => GetAsciiOrUTF8StringNonNullCharacters((ReadOnlySpan<byte>)span);
public static unsafe string GetAsciiOrUTF8StringNonNullCharacters(this ReadOnlySpan<byte> span) public static unsafe string GetAsciiOrUTF8StringNonNullCharacters(this ReadOnlySpan<byte> span)
@ -251,43 +242,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
/// To optimize performance the GET method will be checked first. /// To optimize performance the GET method will be checked first.
/// </remarks> /// </remarks>
/// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool GetKnownMethod(this ReadOnlySpan<byte> span, out HttpMethod method, out int length)
public static unsafe bool GetKnownMethod(this Span<byte> span, out HttpMethod method, out int length)
{ {
fixed (byte* data = span) method = GetKnownMethod(span, out length);
{ return method != HttpMethod.Custom;
method = GetKnownMethod(data, span.Length, out length);
return method != HttpMethod.Custom;
}
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe HttpMethod GetKnownMethod(byte* data, int length, out int methodLength) public static HttpMethod GetKnownMethod(this ReadOnlySpan<byte> span, out int methodLength)
{ {
methodLength = 0; methodLength = 0;
if (length < sizeof(uint)) if (sizeof(uint) <= span.Length)
{ {
return HttpMethod.Custom; if (BinaryPrimitives.ReadUInt32LittleEndian(span) == _httpGetMethodInt)
}
else if (*(uint*)data == _httpGetMethodInt)
{
methodLength = 3;
return HttpMethod.Get;
}
else if (length < sizeof(ulong))
{
return HttpMethod.Custom;
}
else
{
var value = *(ulong*)data;
var key = GetKnownMethodIndex(value);
var x = _knownMethods[key];
if (x != null && (value & x.Item1) == x.Item2)
{ {
methodLength = x.Item4; methodLength = 3;
return x.Item3; return HttpMethod.Get;
}
else if (sizeof(ulong) <= span.Length)
{
var value = BinaryPrimitives.ReadUInt64LittleEndian(span);
var index = GetKnownMethodIndex(value);
var knownMehods = _knownMethods;
if ((uint)index < (uint)knownMehods.Length)
{
var knownMethod = _knownMethods[index];
if (knownMethod != null && (value & knownMethod.Item1) == knownMethod.Item2)
{
methodLength = knownMethod.Item4;
return knownMethod.Item3;
}
}
} }
} }
@ -386,21 +372,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
/// To optimize performance the HTTP/1.1 will be checked first. /// To optimize performance the HTTP/1.1 will be checked first.
/// </remarks> /// </remarks>
/// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool GetKnownVersion(this ReadOnlySpan<byte> span, out HttpVersion knownVersion, out byte length)
public static unsafe bool GetKnownVersion(this Span<byte> span, out HttpVersion knownVersion, out byte length)
{ {
fixed (byte* data = span) knownVersion = GetKnownVersionAndConfirmCR(span);
if (knownVersion != HttpVersion.Unknown)
{ {
knownVersion = GetKnownVersion(data, span.Length); length = sizeof(ulong);
if (knownVersion != HttpVersion.Unknown) return true;
{
length = sizeof(ulong);
return true;
}
length = 0;
return false;
} }
length = 0;
return false;
} }
/// <summary> /// <summary>
@ -415,28 +397,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
/// </remarks> /// </remarks>
/// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe HttpVersion GetKnownVersion(byte* location, int length) internal static HttpVersion GetKnownVersionAndConfirmCR(this ReadOnlySpan<byte> location)
{ {
HttpVersion knownVersion; if (location.Length < sizeof(ulong))
var version = *(ulong*)location;
if (length < sizeof(ulong) + 1 || location[sizeof(ulong)] != (byte)'\r')
{ {
knownVersion = HttpVersion.Unknown; return HttpVersion.Unknown;
}
else if (version == _http11VersionLong)
{
knownVersion = HttpVersion.Http11;
}
else if (version == _http10VersionLong)
{
knownVersion = HttpVersion.Http10;
} }
else else
{ {
knownVersion = HttpVersion.Unknown; var version = BinaryPrimitives.ReadUInt64LittleEndian(location);
if (sizeof(ulong) >= (uint)location.Length || location[sizeof(ulong)] != (byte)'\r')
{
return HttpVersion.Unknown;
}
else if (version == _http11VersionLong)
{
return HttpVersion.Http11;
}
else if (version == _http10VersionLong)
{
return HttpVersion.Http10;
}
} }
return knownVersion; return HttpVersion.Unknown;
} }
/// <summary> /// <summary>

View File

@ -95,7 +95,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, trailers: false, out _consumed, out _examined); 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]);
@ -114,7 +114,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, trailers: false, out _consumed, out _examined)); var exception = Assert.Throws<InvalidOperationException>(() => TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined));
} }
[Fact] [Fact]
@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws<BadHttpRequestException>(() => _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined)); var exception = Assert.Throws<BadHttpRequestException>(() => TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined));
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
_transport.Input.AdvanceTo(_consumed, _examined); _transport.Input.AdvanceTo(_consumed, _examined);
@ -146,7 +146,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws<BadHttpRequestException>(() => _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined)); var exception = Assert.Throws<BadHttpRequestException>(() => TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined));
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
_transport.Input.AdvanceTo(_consumed, _examined); _transport.Input.AdvanceTo(_consumed, _examined);
@ -252,7 +252,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, trailers: false, out _consumed, out _examined); var takeMessageHeaders = TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined);
_transport.Input.AdvanceTo(_consumed, _examined); _transport.Input.AdvanceTo(_consumed, _examined);
Assert.True(takeMessageHeaders); Assert.True(takeMessageHeaders);
@ -264,7 +264,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, trailers: false, out _consumed, out _examined); takeMessageHeaders = TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined);
_transport.Input.AdvanceTo(_consumed, _examined); _transport.Input.AdvanceTo(_consumed, _examined);
Assert.True(takeMessageHeaders); Assert.True(takeMessageHeaders);
@ -389,7 +389,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await _application.Output.WriteAsync(requestLineBytes); await _application.Output.WriteAsync(requestLineBytes);
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
var returnValue = _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined); var returnValue = TakeStartLine(readableBuffer, out _consumed, out _examined);
_transport.Input.AdvanceTo(_consumed, _examined); _transport.Input.AdvanceTo(_consumed, _examined);
Assert.True(returnValue); Assert.True(returnValue);
@ -412,7 +412,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await _application.Output.WriteAsync(requestLineBytes); await _application.Output.WriteAsync(requestLineBytes);
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
var returnValue = _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined); var returnValue = TakeStartLine(readableBuffer, out _consumed, out _examined);
_transport.Input.AdvanceTo(_consumed, _examined); _transport.Input.AdvanceTo(_consumed, _examined);
Assert.True(returnValue); Assert.True(returnValue);
@ -426,7 +426,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{ {
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes("G")); await _application.Output.WriteAsync(Encoding.ASCII.GetBytes("G"));
_http1Connection.ParseRequest((await _transport.Input.ReadAsync()).Buffer, out _consumed, out _examined); ParseRequest((await _transport.Input.ReadAsync()).Buffer, out _consumed, out _examined);
_transport.Input.AdvanceTo(_consumed, _examined); _transport.Input.AdvanceTo(_consumed, _examined);
var expectedRequestHeadersTimeout = _serviceContext.ServerOptions.Limits.RequestHeadersTimeout.Ticks; var expectedRequestHeadersTimeout = _serviceContext.ServerOptions.Limits.RequestHeadersTimeout.Ticks;
@ -443,7 +443,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws<BadHttpRequestException>(() => _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined)); var exception = Assert.Throws<BadHttpRequestException>(() => TakeStartLine(readableBuffer, out _consumed, out _examined));
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
_transport.Input.AdvanceTo(_consumed, _examined); _transport.Input.AdvanceTo(_consumed, _examined);
@ -461,7 +461,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws<BadHttpRequestException>(() => var exception = Assert.Throws<BadHttpRequestException>(() =>
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined)); TakeStartLine(readableBuffer, out _consumed, out _examined));
_transport.Input.AdvanceTo(_consumed, _examined); _transport.Input.AdvanceTo(_consumed, _examined);
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target), exception.Message); Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target), exception.Message);
@ -477,7 +477,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws<BadHttpRequestException>(() => var exception = Assert.Throws<BadHttpRequestException>(() =>
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined)); TakeStartLine(readableBuffer, out _consumed, out _examined));
_transport.Input.AdvanceTo(_consumed, _examined); _transport.Input.AdvanceTo(_consumed, _examined);
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target.EscapeNonPrintable()), exception.Message); Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target.EscapeNonPrintable()), exception.Message);
@ -495,10 +495,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws<BadHttpRequestException>(() => var exception = Assert.Throws<BadHttpRequestException>(() =>
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined)); TakeStartLine(readableBuffer, out _consumed, out _examined));
_transport.Input.AdvanceTo(_consumed, _examined); _transport.Input.AdvanceTo(_consumed, _examined);
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(requestLine.EscapeNonPrintable()), exception.Message); Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(requestLine[..^1].EscapeNonPrintable()), exception.Message);
} }
[Theory] [Theory]
@ -513,7 +513,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws<BadHttpRequestException>(() => var exception = Assert.Throws<BadHttpRequestException>(() =>
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined)); TakeStartLine(readableBuffer, out _consumed, out _examined));
_transport.Input.AdvanceTo(_consumed, _examined); _transport.Input.AdvanceTo(_consumed, _examined);
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target.EscapeNonPrintable()), exception.Message); Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target.EscapeNonPrintable()), exception.Message);
@ -531,7 +531,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws<BadHttpRequestException>(() => var exception = Assert.Throws<BadHttpRequestException>(() =>
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined)); TakeStartLine(readableBuffer, out _consumed, out _examined));
_transport.Input.AdvanceTo(_consumed, _examined); _transport.Input.AdvanceTo(_consumed, _examined);
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target.EscapeNonPrintable()), exception.Message); Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target.EscapeNonPrintable()), exception.Message);
@ -548,7 +548,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws<BadHttpRequestException>(() => var exception = Assert.Throws<BadHttpRequestException>(() =>
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined)); TakeStartLine(readableBuffer, out _consumed, out _examined));
_transport.Input.AdvanceTo(_consumed, _examined); _transport.Input.AdvanceTo(_consumed, _examined);
Assert.Equal(405, exception.StatusCode); Assert.Equal(405, exception.StatusCode);
@ -795,7 +795,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws<BadHttpRequestException>(() => var exception = Assert.Throws<BadHttpRequestException>(() =>
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined)); TakeStartLine(readableBuffer, out _consumed, out _examined));
_transport.Input.AdvanceTo(_consumed, _examined); _transport.Input.AdvanceTo(_consumed, _examined);
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(string.Empty), exception.Message); Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(string.Empty), exception.Message);
@ -971,6 +971,58 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.Equal(CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("a=b"), ex.Message); Assert.Equal(CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("a=b"), ex.Message);
} }
private bool TakeMessageHeaders(ReadOnlySequence<byte> readableBuffer, bool trailers, out SequencePosition consumed, out SequencePosition examined)
{
var reader = new SequenceReader<byte>(readableBuffer);
if (_http1Connection.TakeMessageHeaders(ref reader, trailers: trailers))
{
consumed = reader.Position;
examined = reader.Position;
return true;
}
else
{
consumed = reader.Position;
examined = readableBuffer.End;
return false;
}
}
private bool TakeStartLine(ReadOnlySequence<byte> readableBuffer, out SequencePosition consumed, out SequencePosition examined)
{
var reader = new SequenceReader<byte>(readableBuffer);
if (_http1Connection.TakeStartLine(ref reader))
{
consumed = reader.Position;
examined = reader.Position;
return true;
}
else
{
consumed = reader.Position;
examined = readableBuffer.End;
return false;
}
}
private bool ParseRequest(ReadOnlySequence<byte> readableBuffer, out SequencePosition consumed, out SequencePosition examined)
{
var reader = new SequenceReader<byte>(readableBuffer);
if (_http1Connection.ParseRequest(ref reader))
{
consumed = reader.Position;
examined = reader.Position;
return true;
}
else
{
consumed = reader.Position;
examined = readableBuffer.End;
return false;
}
}
private static async Task WaitForCondition(TimeSpan timeout, Func<bool> condition) private static async Task WaitForCondition(TimeSpan timeout, Func<bool> condition)
{ {
const int MaxWaitLoop = 150; const int MaxWaitLoop = 150;

View File

@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var buffer = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes(requestLine)); var buffer = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes(requestLine));
var requestHandler = new RequestHandler(); var requestHandler = new RequestHandler();
Assert.True(parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined)); Assert.True(ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined));
Assert.Equal(requestHandler.Method, expectedMethod); Assert.Equal(requestHandler.Method, expectedMethod);
Assert.Equal(requestHandler.Version, expectedVersion); Assert.Equal(requestHandler.Version, expectedVersion);
@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var buffer = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes(requestLine)); var buffer = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes(requestLine));
var requestHandler = new RequestHandler(); var requestHandler = new RequestHandler();
Assert.False(parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined)); Assert.False(ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined));
} }
[Theory] [Theory]
@ -71,7 +71,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var buffer = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes(requestLine)); var buffer = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes(requestLine));
var requestHandler = new RequestHandler(); var requestHandler = new RequestHandler();
Assert.False(parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined)); Assert.False(ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined));
Assert.Equal(buffer.Start, consumed); Assert.Equal(buffer.Start, consumed);
Assert.True(buffer.Slice(examined).IsEmpty); Assert.True(buffer.Slice(examined).IsEmpty);
@ -93,9 +93,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws<BadHttpRequestException>(() => var exception = Assert.Throws<BadHttpRequestException>(() =>
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined)); ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined));
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(requestLine.EscapeNonPrintable()), exception.Message); Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(requestLine[..^1].EscapeNonPrintable()), exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode); Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
} }
@ -117,9 +117,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws<BadHttpRequestException>(() => var exception = Assert.Throws<BadHttpRequestException>(() =>
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined)); ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined));
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(method.EscapeNonPrintable() + @" / HTTP/1.1\x0D\x0A"), exception.Message); Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(method.EscapeNonPrintable() + @" / HTTP/1.1\x0D"), exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode); Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
} }
@ -141,7 +141,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws<BadHttpRequestException>(() => var exception = Assert.Throws<BadHttpRequestException>(() =>
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined)); ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined));
Assert.Equal(CoreStrings.FormatBadRequest_UnrecognizedHTTPVersion(httpVersion), exception.Message); Assert.Equal(CoreStrings.FormatBadRequest_UnrecognizedHTTPVersion(httpVersion), exception.Message);
Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode); Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode);
@ -363,7 +363,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws<BadHttpRequestException>(() => var exception = Assert.Throws<BadHttpRequestException>(() =>
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined)); ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined));
Assert.Equal("Invalid request line: ''", exception.Message); Assert.Equal("Invalid request line: ''", exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode); Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
@ -374,7 +374,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
exception = Assert.Throws<BadHttpRequestException>(() => exception = Assert.Throws<BadHttpRequestException>(() =>
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined)); ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined));
Assert.Equal(CoreStrings.FormatBadRequest_UnrecognizedHTTPVersion(string.Empty), exception.Message); Assert.Equal(CoreStrings.FormatBadRequest_UnrecognizedHTTPVersion(string.Empty), exception.Message);
Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode); Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode);
@ -403,7 +403,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Encoding.ASCII.GetBytes("/")); Encoding.ASCII.GetBytes("/"));
var requestHandler = new RequestHandler(); var requestHandler = new RequestHandler();
var result = parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined); var result = ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined);
Assert.False(result); Assert.False(result);
Assert.Equal(buffer.Start, consumed); Assert.Equal(buffer.Start, consumed);
@ -422,7 +422,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var badHttpRequestException = Assert.Throws<BadHttpRequestException>(() => var badHttpRequestException = Assert.Throws<BadHttpRequestException>(() =>
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
{ {
parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined); ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined);
}); });
Assert.Equal(badHttpRequestException.StatusCode, StatusCodes.Status400BadRequest); Assert.Equal(badHttpRequestException.StatusCode, StatusCodes.Status400BadRequest);
@ -480,6 +480,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.True(result); Assert.True(result);
} }
private bool ParseRequestLine(IHttpParser<RequestHandler> parser, RequestHandler requestHandler, ReadOnlySequence<byte> readableBuffer, out SequencePosition consumed, out SequencePosition examined)
{
var reader = new SequenceReader<byte>(readableBuffer);
if (parser.ParseRequestLine(requestHandler, ref reader))
{
consumed = reader.Position;
examined = reader.Position;
return true;
}
else
{
consumed = reader.Position;
examined = readableBuffer.End;
return false;
}
}
private void VerifyHeader( private void VerifyHeader(
string headerName, string headerName,
string rawHeaderValue, string rawHeaderValue,
@ -565,6 +583,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
PathEncoded = pathEncoded; PathEncoded = pathEncoded;
} }
public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
{
var method = versionAndMethod.Method;
var version = versionAndMethod.Version;
var customMethod = startLine[..versionAndMethod.MethodEnd];
var targetStart = targetPath.Offset;
var target = startLine[targetStart..];
var path = target[..targetPath.Length];
var query = target[targetPath.Length..];
Method = method != HttpMethod.Custom ? HttpUtilities.MethodToString(method) : customMethod.GetAsciiStringNonNullCharacters();
Version = HttpUtilities.VersionToString(version);
RawTarget = target.GetAsciiStringNonNullCharacters();
RawPath = path.GetAsciiStringNonNullCharacters();
Query = query.GetAsciiStringNonNullCharacters();
PathEncoded = targetPath.IsEncoded;
}
public void OnStaticIndexedHeader(int index) public void OnStaticIndexedHeader(int index)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

View File

@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{ {
var expectedMethod = (HttpMethod)intExpectedMethod; var expectedMethod = (HttpMethod)intExpectedMethod;
// Arrange // Arrange
var block = new Span<byte>(Encoding.ASCII.GetBytes(input)); var block = new ReadOnlySpan<byte>(Encoding.ASCII.GetBytes(input));
// Act // Act
var result = block.GetKnownMethod(out var knownMethod, out var length); var result = block.GetKnownMethod(out var knownMethod, out var length);
@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{ {
var version = (HttpVersion)intVersion; var version = (HttpVersion)intVersion;
// Arrange // Arrange
var block = new Span<byte>(Encoding.ASCII.GetBytes(input)); var block = new ReadOnlySpan<byte>(Encoding.ASCII.GetBytes(input));
// Act // Act
var result = block.GetKnownVersion(out HttpVersion knownVersion, out var length); var result = block.GetKnownVersion(out HttpVersion knownVersion, out var length);

View File

@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
public void GetsKnownMethod(byte[] methodData, int intExpectedMethod, int expectedLength, bool expectedResult) public void GetsKnownMethod(byte[] methodData, int intExpectedMethod, int expectedLength, bool expectedResult)
{ {
var expectedMethod = (HttpMethod)intExpectedMethod; var expectedMethod = (HttpMethod)intExpectedMethod;
var data = new Span<byte>(methodData); var data = new ReadOnlySpan<byte>(methodData);
var result = data.GetKnownMethod(out var method, out var length); var result = data.GetKnownMethod(out var method, out var length);

View File

@ -38,7 +38,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
Assert.Null(Http1Connection.QueryString); Assert.Null(Http1Connection.QueryString);
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"POST {rawTarget} HTTP/1.1\r\n")); var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"POST {rawTarget} HTTP/1.1\r\n"));
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _)); var reader = new SequenceReader<byte>(ros);
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
// Equal the inputs. // Equal the inputs.
Assert.Equal(rawTarget, Http1Connection.RawTarget); Assert.Equal(rawTarget, Http1Connection.RawTarget);
@ -64,7 +65,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
Assert.Null(Http1Connection.QueryString); Assert.Null(Http1Connection.QueryString);
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"CONNECT {rawTarget} HTTP/1.1\r\n")); var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"CONNECT {rawTarget} HTTP/1.1\r\n"));
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _)); var reader = new SequenceReader<byte>(ros);
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
// Equal the inputs. // Equal the inputs.
Assert.Equal(rawTarget, Http1Connection.RawTarget); Assert.Equal(rawTarget, Http1Connection.RawTarget);
@ -91,7 +93,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
Assert.Null(Http1Connection.QueryString); Assert.Null(Http1Connection.QueryString);
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"CONNECT {rawTarget} HTTP/1.1\r\n")); var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"CONNECT {rawTarget} HTTP/1.1\r\n"));
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _)); var reader = new SequenceReader<byte>(ros);
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
// Equal the inputs. // Equal the inputs.
Assert.Equal(rawTarget, Http1Connection.RawTarget); Assert.Equal(rawTarget, Http1Connection.RawTarget);
@ -117,7 +120,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
Assert.Null(Http1Connection.QueryString); Assert.Null(Http1Connection.QueryString);
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"OPTIONS {rawTarget} HTTP/1.1\r\n")); var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"OPTIONS {rawTarget} HTTP/1.1\r\n"));
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _)); var reader = new SequenceReader<byte>(ros);
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
// Equal the inputs. // Equal the inputs.
Assert.Equal(rawTarget, Http1Connection.RawTarget); Assert.Equal(rawTarget, Http1Connection.RawTarget);
@ -140,7 +144,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
var query = "?q=123&w=xyzw12"; var query = "?q=123&w=xyzw12";
Http1Connection.Reset(); Http1Connection.Reset();
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"POST {rawTarget} HTTP/1.1\r\n")); var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"POST {rawTarget} HTTP/1.1\r\n"));
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _)); var reader = new SequenceReader<byte>(ros);
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
// Equal the inputs. // Equal the inputs.
Assert.Equal(rawTarget, Http1Connection.RawTarget); Assert.Equal(rawTarget, Http1Connection.RawTarget);
@ -184,7 +189,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{ {
Http1Connection.Reset(); Http1Connection.Reset();
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n")); var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n"));
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _)); var reader = new SequenceReader<byte>(ros);
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
var prevRequestUrl = Http1Connection.RawTarget; var prevRequestUrl = Http1Connection.RawTarget;
var prevPath = Http1Connection.Path; var prevPath = Http1Connection.Path;
@ -201,7 +207,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
// Parser decodes % encoding in place, so we need to recreate the ROS // Parser decodes % encoding in place, so we need to recreate the ROS
ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n")); ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n"));
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _)); reader = new SequenceReader<byte>(ros);
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
// Equal the inputs. // Equal the inputs.
Assert.Equal(rawTarget, Http1Connection.RawTarget); Assert.Equal(rawTarget, Http1Connection.RawTarget);
@ -234,7 +241,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
Http1Connection.Reset(); Http1Connection.Reset();
ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n")); ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n"));
Parser.ParseRequestLine(ParsingHandler, ros, out _, out _); reader = new SequenceReader<byte>(ros);
Parser.ParseRequestLine(ParsingHandler, ref reader);
// Equal the inputs. // Equal the inputs.
Assert.Equal(rawTarget, Http1Connection.RawTarget); Assert.Equal(rawTarget, Http1Connection.RawTarget);
@ -275,7 +283,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{ {
Http1Connection.Reset(); Http1Connection.Reset();
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n")); var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n"));
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _)); var reader = new SequenceReader<byte>(ros);
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
var prevRequestUrl = Http1Connection.RawTarget; var prevRequestUrl = Http1Connection.RawTarget;
var prevPath = Http1Connection.Path; var prevPath = Http1Connection.Path;
@ -291,7 +300,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
Assert.Null(Http1Connection.QueryString); Assert.Null(Http1Connection.QueryString);
ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n")); ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n"));
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _)); reader = new SequenceReader<byte>(ros);
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
// Equal the inputs. // Equal the inputs.
Assert.Equal(rawTarget, Http1Connection.RawTarget); Assert.Equal(rawTarget, Http1Connection.RawTarget);
@ -324,7 +334,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
Http1Connection.Reset(); Http1Connection.Reset();
ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n")); ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n"));
Parser.ParseRequestLine(ParsingHandler, ros, out _, out _); reader = new SequenceReader<byte>(ros);
Parser.ParseRequestLine(ParsingHandler, ref reader);
// Equal the inputs. // Equal the inputs.
Assert.Equal(rawTarget, Http1Connection.RawTarget); Assert.Equal(rawTarget, Http1Connection.RawTarget);
@ -353,7 +364,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
Http1Connection.Reset(); Http1Connection.Reset();
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"OPTIONS {rawTarget} HTTP/1.1\r\n")); var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"OPTIONS {rawTarget} HTTP/1.1\r\n"));
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _)); var reader = new SequenceReader<byte>(ros);
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
var prevRequestUrl = Http1Connection.RawTarget; var prevRequestUrl = Http1Connection.RawTarget;
var prevPath = Http1Connection.Path; var prevPath = Http1Connection.Path;
@ -369,7 +381,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
Assert.Null(Http1Connection.QueryString); Assert.Null(Http1Connection.QueryString);
ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"OPTIONS {rawTarget} HTTP/1.1\r\n")); ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"OPTIONS {rawTarget} HTTP/1.1\r\n"));
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _)); reader = new SequenceReader<byte>(ros);
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
// Equal the inputs. // Equal the inputs.
Assert.Equal(rawTarget, Http1Connection.RawTarget); Assert.Equal(rawTarget, Http1Connection.RawTarget);
@ -400,7 +413,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
Http1Connection.Reset(); Http1Connection.Reset();
ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n")); ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n"));
Parser.ParseRequestLine(ParsingHandler, ros, out _, out _); reader = new SequenceReader<byte>(ros);
Parser.ParseRequestLine(ParsingHandler, ref reader);
// Equal the inputs. // Equal the inputs.
Assert.Equal(rawTarget, Http1Connection.RawTarget); Assert.Equal(rawTarget, Http1Connection.RawTarget);
@ -431,7 +445,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{ {
Http1Connection.Reset(); Http1Connection.Reset();
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"CONNECT {rawTarget} HTTP/1.1\r\n")); var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"CONNECT {rawTarget} HTTP/1.1\r\n"));
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _)); var reader = new SequenceReader<byte>(ros);
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
var prevRequestUrl = Http1Connection.RawTarget; var prevRequestUrl = Http1Connection.RawTarget;
var prevPath = Http1Connection.Path; var prevPath = Http1Connection.Path;
@ -447,7 +462,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
Assert.Null(Http1Connection.QueryString); Assert.Null(Http1Connection.QueryString);
ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"CONNECT {rawTarget} HTTP/1.1\r\n")); ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"CONNECT {rawTarget} HTTP/1.1\r\n"));
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _)); reader = new SequenceReader<byte>(ros);
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
// Equal the inputs. // Equal the inputs.
Assert.Equal(rawTarget, Http1Connection.RawTarget); Assert.Equal(rawTarget, Http1Connection.RawTarget);
@ -478,7 +494,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
Http1Connection.Reset(); Http1Connection.Reset();
ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"CONNECT {rawTarget} HTTP/1.1\r\n")); ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"CONNECT {rawTarget} HTTP/1.1\r\n"));
Parser.ParseRequestLine(ParsingHandler, ros, out _, out _); reader = new SequenceReader<byte>(ros);
Parser.ParseRequestLine(ParsingHandler, ref reader);
// Equal the inputs. // Equal the inputs.
Assert.Equal(rawTarget, Http1Connection.RawTarget); Assert.Equal(rawTarget, Http1Connection.RawTarget);

View File

@ -79,14 +79,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
private void ParseData() private void ParseData()
{ {
if (!_parser.ParseRequestLine(new Adapter(this), _buffer, out var consumed, out var examined)) var reader = new SequenceReader<byte>(_buffer);
if (!_parser.ParseRequestLine(new Adapter(this), ref reader))
{ {
ErrorUtilities.ThrowInvalidRequestHeaders(); ErrorUtilities.ThrowInvalidRequestHeaders();
} }
_buffer = _buffer.Slice(consumed, _buffer.End);
var reader = new SequenceReader<byte>(_buffer);
if (!_parser.ParseHeaders(new Adapter(this), ref reader)) if (!_parser.ParseHeaders(new Adapter(this), ref reader))
{ {
ErrorUtilities.ThrowInvalidRequestHeaders(); ErrorUtilities.ThrowInvalidRequestHeaders();
@ -112,8 +110,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
public void OnHeadersComplete(bool endStream) public void OnHeadersComplete(bool endStream)
=> RequestHandler.Connection.OnHeadersComplete(); => RequestHandler.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(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
=> RequestHandler.Connection.OnStartLine(method, version, target, path, query, customMethod, pathEncoded); => RequestHandler.Connection.OnStartLine(versionAndMethod, targetPath, startLine);
public void OnStaticIndexedHeader(int index) public void OnStaticIndexedHeader(int index)
{ {

View File

@ -77,12 +77,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{ {
_http1Connection.Reset(); _http1Connection.Reset();
if (!_http1Connection.TakeStartLine(_buffer, out var consumed, out var examined)) var reader = new SequenceReader<byte>(_buffer);
if (!_http1Connection.TakeStartLine(ref reader))
{ {
ErrorUtilities.ThrowInvalidRequestLine(); ErrorUtilities.ThrowInvalidRequestLine();
} }
if (!_http1Connection.TakeMessageHeaders(_buffer, trailers: false, out consumed, out examined)) if (!_http1Connection.TakeMessageHeaders(ref reader, trailers: false))
{ {
ErrorUtilities.ThrowInvalidRequestHeaders(); ErrorUtilities.ThrowInvalidRequestHeaders();
} }
@ -92,7 +93,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{ {
_http1Connection.Reset(); _http1Connection.Reset();
if (!_http1Connection.TakeStartLine(_buffer, out var consumed, out var examined)) var reader = new SequenceReader<byte>(_buffer);
if (!_http1Connection.TakeStartLine(ref reader))
{ {
ErrorUtilities.ThrowInvalidRequestLine(); ErrorUtilities.ThrowInvalidRequestLine();
} }
@ -102,7 +104,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{ {
_http1Connection.Reset(); _http1Connection.Reset();
if (!_http1Connection.TakeMessageHeaders(_buffer, trailers: false, out var consumed, out var examined)) var reader = new SequenceReader<byte>(_buffer);
if (!_http1Connection.TakeMessageHeaders(ref reader, trailers: false))
{ {
ErrorUtilities.ThrowInvalidRequestHeaders(); ErrorUtilities.ThrowInvalidRequestHeaders();
} }

View File

@ -10,7 +10,7 @@ using HttpMethod = Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMe
namespace Microsoft.AspNetCore.Server.Kestrel.Performance namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{ {
internal class HttpParserBenchmark : IHttpRequestLineHandler, IHttpHeadersHandler public class HttpParserBenchmark : IHttpRequestLineHandler, IHttpHeadersHandler
{ {
private readonly HttpParser<Adapter> _parser = new HttpParser<Adapter>(); private readonly HttpParser<Adapter> _parser = new HttpParser<Adapter>();
@ -63,21 +63,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
private void ParseData() private void ParseData()
{ {
if (!_parser.ParseRequestLine(new Adapter(this), _buffer, out var consumed, out var examined)) var reader = new SequenceReader<byte>(_buffer);
if (!_parser.ParseRequestLine(new Adapter(this), ref reader))
{ {
ErrorUtilities.ThrowInvalidRequestHeaders(); ErrorUtilities.ThrowInvalidRequestHeaders();
} }
_buffer = _buffer.Slice(consumed, _buffer.End);
var reader = new SequenceReader<byte>(_buffer);
if (!_parser.ParseHeaders(new Adapter(this), ref reader)) if (!_parser.ParseHeaders(new Adapter(this), ref reader))
{ {
ErrorUtilities.ThrowInvalidRequestHeaders(); ErrorUtilities.ThrowInvalidRequestHeaders();
} }
} }
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded) public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
{ {
} }
@ -114,8 +112,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
public void OnHeadersComplete(bool endStream) public void OnHeadersComplete(bool endStream)
=> RequestHandler.OnHeadersComplete(endStream); => RequestHandler.OnHeadersComplete(endStream);
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded) public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
=> RequestHandler.OnStartLine(method, version, target, path, query, customMethod, pathEncoded); => RequestHandler.OnStartLine(versionAndMethod, targetPath, startLine);
public void OnStaticIndexedHeader(int index) public void OnStaticIndexedHeader(int index)
{ {

View File

@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
return GetKnownMethod(data); return GetKnownMethod(data);
} }
private int GetKnownMethod(Span<byte> data) private int GetKnownMethod(ReadOnlySpan<byte> data)
{ {
int len = 0; int len = 0;
HttpMethod method; HttpMethod method;
@ -129,7 +129,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{ {
int len = 0; int len = 0;
HttpVersion version; HttpVersion version;
Span<byte> data = _version; ReadOnlySpan<byte> data = _version;
for (int i = 0; i < loops; i++) for (int i = 0; i < loops; i++)
{ {
data.GetKnownVersion(out version, out var length); data.GetKnownVersion(out version, out var length);

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Net.Http;
using System.Text; using System.Text;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using HttpMethod = Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod; using HttpMethod = Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod;
@ -13,7 +12,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
internal class NullParser<TRequestHandler> : IHttpParser<TRequestHandler> where TRequestHandler : struct, IHttpHeadersHandler, IHttpRequestLineHandler internal class NullParser<TRequestHandler> : IHttpParser<TRequestHandler> where TRequestHandler : struct, IHttpHeadersHandler, IHttpRequestLineHandler
{ {
private readonly byte[] _startLine = Encoding.ASCII.GetBytes("GET /plaintext HTTP/1.1\r\n"); private readonly byte[] _startLine = Encoding.ASCII.GetBytes("GET /plaintext HTTP/1.1\r\n");
private readonly byte[] _target = Encoding.ASCII.GetBytes("/plaintext");
private readonly byte[] _hostHeaderName = Encoding.ASCII.GetBytes("Host"); private readonly byte[] _hostHeaderName = Encoding.ASCII.GetBytes("Host");
private readonly byte[] _hostHeaderValue = Encoding.ASCII.GetBytes("www.example.com"); private readonly byte[] _hostHeaderValue = Encoding.ASCII.GetBytes("www.example.com");
private readonly byte[] _acceptHeaderName = Encoding.ASCII.GetBytes("Accept"); private readonly byte[] _acceptHeaderName = Encoding.ASCII.GetBytes("Accept");
@ -33,18 +31,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
return true; return true;
} }
public bool ParseRequestLine(TRequestHandler handler, in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined) public bool ParseRequestLine(TRequestHandler handler, ref SequenceReader<byte> reader)
{ {
handler.OnStartLine(HttpMethod.Get, Span<byte> startLine = _startLine;
HttpVersion.Http11,
new Span<byte>(_target),
new Span<byte>(_target),
Span<byte>.Empty,
Span<byte>.Empty,
false);
consumed = buffer.Start; handler.OnStartLine(
examined = buffer.End; new HttpVersionAndMethod(HttpMethod.Get, 3) { Version = HttpVersion.Http11 },
new TargetOffsetPathLength(3, startLine.Length - 3, false),
startLine);
return true; return true;
} }

View File

@ -147,25 +147,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
} }
var readableBuffer = awaitable.GetAwaiter().GetResult().Buffer; var readableBuffer = awaitable.GetAwaiter().GetResult().Buffer;
var reader = new SequenceReader<byte>(readableBuffer);
do do
{ {
Http1Connection.Reset(); Http1Connection.Reset();
if (!Http1Connection.TakeStartLine(readableBuffer, out var consumed, out var examined)) if (!Http1Connection.TakeStartLine(ref reader))
{ {
ErrorUtilities.ThrowInvalidRequestLine(); ErrorUtilities.ThrowInvalidRequestLine();
} }
readableBuffer = readableBuffer.Slice(consumed); if (!Http1Connection.TakeMessageHeaders(ref reader, trailers: false))
if (!Http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out consumed, out examined))
{ {
ErrorUtilities.ThrowInvalidRequestHeaders(); ErrorUtilities.ThrowInvalidRequestHeaders();
} }
readableBuffer = readableBuffer.Slice(consumed);
} }
while (readableBuffer.Length > 0); while (!reader.End);
Pipe.Reader.AdvanceTo(readableBuffer.End); Pipe.Reader.AdvanceTo(readableBuffer.End);
} }
@ -183,23 +180,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
var result = awaitable.GetAwaiter().GetResult(); var result = awaitable.GetAwaiter().GetResult();
var readableBuffer = result.Buffer; var readableBuffer = result.Buffer;
var reader = new SequenceReader<byte>(readableBuffer);
Http1Connection.Reset(); Http1Connection.Reset();
if (!Http1Connection.TakeStartLine(readableBuffer, out var consumed, out var examined)) if (!Http1Connection.TakeStartLine(ref reader))
{ {
ErrorUtilities.ThrowInvalidRequestLine(); ErrorUtilities.ThrowInvalidRequestLine();
} }
Pipe.Reader.AdvanceTo(consumed, examined); Pipe.Reader.AdvanceTo(reader.Position, reader.Position);
result = Pipe.Reader.ReadAsync().GetAwaiter().GetResult(); result = Pipe.Reader.ReadAsync().GetAwaiter().GetResult();
readableBuffer = result.Buffer; readableBuffer = result.Buffer;
reader = new SequenceReader<byte>(readableBuffer);
if (!Http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out consumed, out examined)) if (!Http1Connection.TakeMessageHeaders(ref reader, trailers: false))
{ {
ErrorUtilities.ThrowInvalidRequestHeaders(); ErrorUtilities.ThrowInvalidRequestHeaders();
} }
Pipe.Reader.AdvanceTo(consumed, examined); Pipe.Reader.AdvanceTo(reader.Position, reader.Position);
} }
while (true); while (true);
} }

View File

@ -238,7 +238,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
foreach (var requestLine in HttpParsingData.RequestLineInvalidData) foreach (var requestLine in HttpParsingData.RequestLineInvalidData)
{ {
data.Add(requestLine, CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(requestLine.EscapeNonPrintable())); data.Add(requestLine, CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(requestLine[..^1].EscapeNonPrintable()));
} }
foreach (var target in HttpParsingData.TargetWithEncodedNullCharData) foreach (var target in HttpParsingData.TargetWithEncodedNullCharData)