diff --git a/.travis.yml b/.travis.yml
index 33d8f34d20..4d388988b4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -32,5 +32,5 @@ branches:
before_install:
- if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/; fi
script:
- - ./build.sh --quiet verify
+ - ./build.sh
- if test "$TRAVIS_OS_NAME" != "osx"; then bash test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/SystemdActivation/docker.sh; fi
diff --git a/NuGet.config b/NuGet.config
index 69bab2da9a..93f1ac47df 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -3,7 +3,6 @@
-
diff --git a/appveyor.yml b/appveyor.yml
index 61e1ba06b7..d622af030a 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -8,7 +8,7 @@ branches:
- feature/dev-si
- /^(.*\/)?ci-.*$/
build_script:
- - build.cmd --quiet verify
+ - ps: .\build.ps1
clone_depth: 1
test: off
deploy: off
diff --git a/build.ps1 b/build.ps1
index f780c43a82..5bf0e2c113 100644
--- a/build.ps1
+++ b/build.ps1
@@ -33,7 +33,7 @@ cd $PSScriptRoot
$repoFolder = $PSScriptRoot
$env:REPO_FOLDER = $repoFolder
-$koreBuildZip="https://github.com/aspnet/KoreBuild/archive/feature/msbuild.zip"
+$koreBuildZip="https://github.com/aspnet/KoreBuild/archive/dev.zip"
if ($env:KOREBUILD_ZIP)
{
$koreBuildZip=$env:KOREBUILD_ZIP
@@ -64,4 +64,4 @@ if (!(Test-Path $buildFolder)) {
}
}
-&"$buildFile" $args
\ No newline at end of file
+&"$buildFile" @args
diff --git a/build.sh b/build.sh
index ff79789196..b0bcadb579 100755
--- a/build.sh
+++ b/build.sh
@@ -2,7 +2,7 @@
repoFolder="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $repoFolder
-koreBuildZip="https://github.com/aspnet/KoreBuild/archive/feature/msbuild.zip"
+koreBuildZip="https://github.com/aspnet/KoreBuild/archive/dev.zip"
if [ ! -z $KOREBUILD_ZIP ]; then
koreBuildZip=$KOREBUILD_ZIP
fi
@@ -43,4 +43,4 @@ if test ! -d $buildFolder; then
fi
fi
-$buildFile -r $repoFolder "$@"
\ No newline at end of file
+$buildFile -r $repoFolder "$@"
diff --git a/build/dependencies.props b/build/dependencies.props
index 9aa824128d..3c441802d9 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -3,5 +3,7 @@
1.6.1
4.3.0
1.9.1
+ 0.1.0-*
+ 0.1.0-*
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/BadHttpRequestException.cs b/src/Microsoft.AspNetCore.Server.Kestrel/BadHttpRequestException.cs
index f7109710c6..54533f05e1 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/BadHttpRequestException.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel/BadHttpRequestException.cs
@@ -25,9 +25,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel
case RequestRejectionReason.HeadersCorruptedInvalidHeaderSequence:
ex = new BadHttpRequestException("Headers corrupted, invalid header sequence.", StatusCodes.Status400BadRequest);
break;
- case RequestRejectionReason.HeaderLineMustNotStartWithWhitespace:
- ex = new BadHttpRequestException("Header line must not start with whitespace.", StatusCodes.Status400BadRequest);
- break;
case RequestRejectionReason.NoColonCharacterFoundInHeaderLine:
ex = new BadHttpRequestException("No ':' character found in header line.", StatusCodes.Status400BadRequest);
break;
@@ -40,6 +37,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel
case RequestRejectionReason.HeaderValueLineFoldingNotSupported:
ex = new BadHttpRequestException("Header value line folding not supported.", StatusCodes.Status400BadRequest);
break;
+ case RequestRejectionReason.InvalidRequestLine:
+ ex = new BadHttpRequestException("Invalid request line.", StatusCodes.Status400BadRequest);
+ break;
case RequestRejectionReason.MalformedRequestInvalidHeaders:
ex = new BadHttpRequestException("Malformed request: invalid headers.", StatusCodes.Status400BadRequest);
break;
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs
index c87c72c468..463ce0a237 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs
@@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
private CancellationTokenSource _abortedCts;
private CancellationToken? _manuallySetRequestAbortToken;
- private RequestProcessingStatus _requestProcessingStatus;
+ protected RequestProcessingStatus _requestProcessingStatus;
protected bool _keepAlive;
protected bool _upgrade;
private bool _canHaveBody;
@@ -982,15 +982,46 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
Output.ProducingComplete(end);
}
+ public void ParseRequest(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
+ {
+ consumed = buffer.Start;
+ examined = buffer.End;
+
+ switch (_requestProcessingStatus)
+ {
+ case RequestProcessingStatus.RequestPending:
+ if (buffer.IsEmpty)
+ {
+ break;
+ }
+
+ ConnectionControl.ResetTimeout(_requestHeadersTimeoutMilliseconds, TimeoutAction.SendTimeoutResponse);
+
+ _requestProcessingStatus = RequestProcessingStatus.ParsingRequestLine;
+ goto case RequestProcessingStatus.ParsingRequestLine;
+ case RequestProcessingStatus.ParsingRequestLine:
+ if (TakeStartLine(buffer, out consumed, out examined))
+ {
+ buffer = buffer.Slice(consumed, buffer.End);
+
+ _requestProcessingStatus = RequestProcessingStatus.ParsingHeaders;
+ goto case RequestProcessingStatus.ParsingHeaders;
+ }
+ else
+ {
+ break;
+ }
+ case RequestProcessingStatus.ParsingHeaders:
+ if (TakeMessageHeaders(buffer, out consumed, out examined))
+ {
+ _requestProcessingStatus = RequestProcessingStatus.AppStarted;
+ }
+ break;
+ }
+ }
+
public bool TakeStartLine(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
{
- if (_requestProcessingStatus == RequestProcessingStatus.RequestPending)
- {
- ConnectionControl.ResetTimeout(_requestHeadersTimeoutMilliseconds, TimeoutAction.SendTimeoutResponse);
- }
-
- _requestProcessingStatus = RequestProcessingStatus.RequestStarted;
-
var overLength = false;
if (buffer.Length >= ServerOptions.Limits.MaxRequestLineSize)
{
@@ -1039,7 +1070,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
return true;
}
- public bool TakeMessageHeaders(ReadableBuffer buffer, FrameRequestHeaders requestHeaders, out ReadCursor consumed, out ReadCursor examined)
+ public bool TakeMessageHeaders(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
{
// Make sure the buffer is limited
bool overLength = false;
@@ -1085,7 +1116,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (!appCompleted)
{
// Back out of header creation surface exeception in user code
- _requestProcessingStatus = RequestProcessingStatus.RequestStarted;
+ _requestProcessingStatus = RequestProcessingStatus.AppStarted;
throw ex;
}
else
@@ -1141,18 +1172,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
public void RejectRequest(RequestRejectionReason reason)
{
- RejectRequest(BadHttpRequestException.GetException(reason));
+ throw BadHttpRequestException.GetException(reason);
}
public void RejectRequest(RequestRejectionReason reason, string value)
{
- RejectRequest(BadHttpRequestException.GetException(reason, value));
- }
-
- private void RejectRequest(BadHttpRequestException ex)
- {
- Log.ConnectionBadRequest(ConnectionId, ex);
- throw ex;
+ throw BadHttpRequestException.GetException(reason, value);
}
public void SetBadRequestState(RequestRejectionReason reason)
@@ -1162,6 +1187,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
public void SetBadRequestState(BadHttpRequestException ex)
{
+ Log.ConnectionBadRequest(ConnectionId, ex);
+
if (!HasResponseStarted)
{
SetErrorResponseHeaders(ex.StatusCode);
@@ -1190,20 +1217,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
Log.ApplicationError(ConnectionId, ex);
}
- public enum RequestLineStatus
- {
- Empty,
- Incomplete,
- Done
- }
-
- private enum RequestProcessingStatus
- {
- RequestPending,
- RequestStarted,
- ResponseStarted
- }
-
public void OnStartLine(HttpMethod method, HttpVersion version, Span target, Span path, Span query, Span customMethod)
{
// URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11
@@ -1215,7 +1228,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (needDecode)
{
// Read raw target before mutating memory.
- rawTarget = target.GetAsciiString() ?? string.Empty;
+ rawTarget = target.GetAsciiStringNonNullCharacters();
// URI was encoded, unescape and then parse as utf8
int pathLength = UrlEncoder.Decode(path, path);
@@ -1224,7 +1237,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
else
{
// URI wasn't encoded, parse as ASCII
- requestUrlPath = path.GetAsciiString() ?? string.Empty;
+ requestUrlPath = path.GetAsciiStringNonNullCharacters();
if (query.Length == 0)
{
@@ -1234,21 +1247,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
else
{
- rawTarget = target.GetAsciiString() ?? string.Empty;
+ rawTarget = target.GetAsciiStringNonNullCharacters();
}
}
var normalizedTarget = PathNormalizer.RemoveDotSegments(requestUrlPath);
if (method != HttpMethod.Custom)
{
- Method = HttpUtilities.MethodToString(method) ?? String.Empty;
+ Method = HttpUtilities.MethodToString(method) ?? string.Empty;
}
else
{
- Method = customMethod.GetAsciiString() ?? string.Empty;
+ Method = customMethod.GetAsciiStringNonNullCharacters();
}
- QueryString = query.GetAsciiString() ?? string.Empty;
+ QueryString = query.GetAsciiStringNonNullCharacters();
RawTarget = rawTarget;
HttpVersion = HttpUtilities.VersionToString(version);
@@ -1276,7 +1289,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
RejectRequest(RequestRejectionReason.TooManyHeaders);
}
- var valueString = value.GetAsciiString() ?? string.Empty;
+ var valueString = value.GetAsciiStringNonNullCharacters();
FrameRequestHeaders.Append(name, valueString);
}
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameOfT.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameOfT.cs
index 23626c47ac..bf9d21e7ab 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameOfT.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameOfT.cs
@@ -3,7 +3,6 @@
using System;
using System.IO;
-using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server;
@@ -31,16 +30,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
///
public override async Task RequestProcessingAsync()
{
- var requestLineStatus = default(RequestLineStatus);
-
try
{
while (!_requestProcessingStopping)
{
- // If writer completes with an error Input.ReadAsyncDispatched would throw and
- // this would not be reset to empty. But it's required by ECONNRESET check lower in the method.
- requestLineStatus = RequestLineStatus.Empty;
-
ConnectionControl.SetTimeout(_keepAliveMilliseconds, TimeoutAction.CloseConnection);
while (!_requestProcessingStopping)
@@ -49,76 +42,44 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var examined = result.Buffer.End;
var consumed = result.Buffer.End;
+ InitializeHeaders();
+
try
{
- if (!result.Buffer.IsEmpty)
- {
- requestLineStatus = TakeStartLine(result.Buffer, out consumed, out examined)
- ? RequestLineStatus.Done : RequestLineStatus.Incomplete;
- }
- else
- {
- requestLineStatus = RequestLineStatus.Empty;
- }
+ ParseRequest(result.Buffer, out consumed, out examined);
}
catch (InvalidOperationException)
{
- throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidRequestLine);
+ switch (_requestProcessingStatus)
+ {
+ case RequestProcessingStatus.ParsingRequestLine:
+ throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidRequestLine);
+ case RequestProcessingStatus.ParsingHeaders:
+ throw BadHttpRequestException.GetException(RequestRejectionReason.MalformedRequestInvalidHeaders);
+ }
+ throw;
}
finally
{
Input.Reader.Advance(consumed, examined);
}
- if (requestLineStatus == RequestLineStatus.Done)
+ if (_requestProcessingStatus == RequestProcessingStatus.AppStarted)
{
break;
}
if (result.IsCompleted)
{
- if (requestLineStatus == RequestLineStatus.Empty)
+ switch (_requestProcessingStatus)
{
- return;
+ case RequestProcessingStatus.RequestPending:
+ return;
+ case RequestProcessingStatus.ParsingRequestLine:
+ throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidRequestLine);
+ case RequestProcessingStatus.ParsingHeaders:
+ throw BadHttpRequestException.GetException(RequestRejectionReason.MalformedRequestInvalidHeaders);
}
-
- RejectRequest(RequestRejectionReason.InvalidRequestLine, requestLineStatus.ToString());
- }
- }
-
- InitializeHeaders();
-
- while (!_requestProcessingStopping)
- {
-
- var result = await Input.Reader.ReadAsync();
- var examined = result.Buffer.End;
- var consumed = result.Buffer.End;
-
- bool headersDone;
-
- try
- {
- headersDone = TakeMessageHeaders(result.Buffer, FrameRequestHeaders, out consumed,
- out examined);
- }
- catch (InvalidOperationException)
- {
- throw BadHttpRequestException.GetException(RequestRejectionReason.MalformedRequestInvalidHeaders);
- }
- finally
- {
- Input.Reader.Advance(consumed, examined);
- }
-
- if (headersDone)
- {
- break;
- }
-
- if (result.IsCompleted)
- {
- RejectRequest(RequestRejectionReason.MalformedRequestInvalidHeaders);
}
}
@@ -231,7 +192,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
catch (IOException ex) when (ex.InnerException is UvException)
{
// Don't log ECONNRESET errors made between requests. Browsers like IE will reset connections regularly.
- if (requestLineStatus != RequestLineStatus.Empty ||
+ if (_requestProcessingStatus != RequestProcessingStatus.RequestPending ||
((UvException)ex.InnerException).StatusCode != Constants.ECONNRESET)
{
Log.RequestProcessingError(ConnectionId, ex);
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/KestrelHttpParser.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/KestrelHttpParser.cs
index 662fd8aabe..a5ce732aec 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/KestrelHttpParser.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/KestrelHttpParser.cs
@@ -2,7 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Buffers;
using System.IO.Pipelines;
+using System.Numerics;
+using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.Extensions.Logging;
@@ -31,38 +34,36 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
consumed = buffer.Start;
examined = buffer.End;
- var start = buffer.Start;
ReadCursor end;
- if (ReadCursorOperations.Seek(start, buffer.End, out end, ByteLF) == -1)
- {
- return false;
- }
-
- const int stackAllocLimit = 512;
-
- // Move 1 byte past the \n
- end = buffer.Move(end, 1);
- var startLineBuffer = buffer.Slice(start, end);
-
Span span;
- if (startLineBuffer.IsSingleSpan)
+ // If the buffer is a single span then use it to find the LF
+ if (buffer.IsSingleSpan)
{
- // No copies, directly use the one and only span
- span = startLineBuffer.First.Span;
- }
- else if (startLineBuffer.Length < stackAllocLimit)
- {
- // Multiple buffers and < stackAllocLimit, copy into a stack buffer
- byte* stackBuffer = stackalloc byte[startLineBuffer.Length];
- span = new Span(stackBuffer, startLineBuffer.Length);
- startLineBuffer.CopyTo(span);
+ var startLineSpan = buffer.First.Span;
+ var lineIndex = startLineSpan.IndexOfVectorized(ByteLF);
+
+ if (lineIndex == -1)
+ {
+ return false;
+ }
+
+ end = buffer.Move(consumed, lineIndex + 1);
+ span = startLineSpan.Slice(0, lineIndex + 1);
}
else
{
- // We're not a single span here but we can use pooled arrays to avoid allocations in the rare case
- span = new Span(new byte[startLineBuffer.Length]);
- startLineBuffer.CopyTo(span);
+ var start = buffer.Start;
+ if (ReadCursorOperations.Seek(start, buffer.End, out end, ByteLF) == -1)
+ {
+ return false;
+ }
+
+ // Move 1 byte past the \n
+ end = buffer.Move(end, 1);
+ var startLineBuffer = buffer.Slice(start, end);
+
+ span = startLineBuffer.ToSpan();
}
var pathStart = -1;
@@ -71,37 +72,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var pathEnd = -1;
var versionStart = -1;
- HttpVersion httpVersion = HttpVersion.Unknown;
- HttpMethod method = HttpMethod.Custom;
+ var httpVersion = HttpVersion.Unknown;
+ HttpMethod method;
Span customMethod;
- var state = StartLineState.KnownMethod;
+ var i = 0;
+ var length = span.Length;
+ var done = false;
- int i;
fixed (byte* data = &span.DangerousGetPinnableReference())
{
- var length = span.Length;
- for (i = 0; i < length; i++)
+ switch (StartLineState.KnownMethod)
{
- var ch = data[i];
+ case StartLineState.KnownMethod:
+ if (span.GetKnownMethod(out method, out var methodLength))
+ {
+ // Update the index, current char, state and jump directly
+ // to the next state
+ i += methodLength + 1;
- switch (state)
- {
- case StartLineState.KnownMethod:
- if (span.GetKnownMethod(out method, out var methodLength))
- {
- // Update the index, current char, state and jump directly
- // to the next state
- i += methodLength + 1;
- ch = data[i];
- state = StartLineState.Path;
+ goto case StartLineState.Path;
+ }
+ goto case StartLineState.UnknownMethod;
- goto case StartLineState.Path;
- }
+ case StartLineState.UnknownMethod:
+ for (; i < length; i++)
+ {
+ var ch = data[i];
- state = StartLineState.UnknownMethod;
- goto case StartLineState.UnknownMethod;
-
- case StartLineState.UnknownMethod:
if (ch == ByteSpace)
{
customMethod = span.Slice(0, i);
@@ -110,16 +107,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
RejectRequestLine(span);
}
+ // Consume space
+ i++;
- state = StartLineState.Path;
+ goto case StartLineState.Path;
}
- else if (!IsValidTokenChar((char)ch))
+
+ if (!IsValidTokenChar((char)ch))
{
RejectRequestLine(span);
}
+ }
- break;
- case StartLineState.Path:
+ break;
+ case StartLineState.Path:
+ for (; i < length; i++)
+ {
+ var ch = data[i];
if (ch == ByteSpace)
{
pathEnd = i;
@@ -133,7 +137,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
// No query string found
queryStart = queryEnd = i;
- state = StartLineState.KnownVersion;
+ // Consume space
+ i++;
+
+ goto case StartLineState.KnownVersion;
}
else if (ch == ByteQuestionMark)
{
@@ -146,7 +153,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
queryStart = i;
- state = StartLineState.QueryString;
+ goto case StartLineState.QueryString;
}
else if (ch == BytePercentage)
{
@@ -160,35 +167,42 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
pathStart = i;
}
- break;
-
- case StartLineState.QueryString:
+ }
+ break;
+ case StartLineState.QueryString:
+ for (; i < length; i++)
+ {
+ var ch = data[i];
if (ch == ByteSpace)
{
queryEnd = i;
- state = StartLineState.KnownVersion;
+ // Consume space
+ i++;
+
+ goto case StartLineState.KnownVersion;
}
- break;
- case StartLineState.KnownVersion:
- // REVIEW: We don't *need* to slice here but it makes the API
- // nicer, slicing should be free :)
- if (span.Slice(i).GetKnownVersion(out httpVersion, out var versionLenght))
- {
- // Update the index, current char, state and jump directly
- // to the next state
- i += versionLenght + 1;
- ch = data[i];
- state = StartLineState.NewLine;
+ }
+ break;
+ case StartLineState.KnownVersion:
+ // REVIEW: We don't *need* to slice here but it makes the API
+ // nicer, slicing should be free :)
+ if (span.Slice(i).GetKnownVersion(out httpVersion, out var versionLenght))
+ {
+ // Update the index, current char, state and jump directly
+ // to the next state
+ i += versionLenght + 1;
+ goto case StartLineState.NewLine;
+ }
- goto case StartLineState.NewLine;
- }
+ versionStart = i;
- versionStart = i;
- state = StartLineState.UnknownVersion;
- goto case StartLineState.UnknownVersion;
+ goto case StartLineState.UnknownVersion;
- case StartLineState.UnknownVersion:
+ case StartLineState.UnknownVersion:
+ for (; i < length; i++)
+ {
+ var ch = data[i];
if (ch == ByteCR)
{
var versionSpan = span.Slice(versionStart, i - versionStart);
@@ -199,25 +213,27 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
else
{
- RejectRequest(RequestRejectionReason.UnrecognizedHTTPVersion, versionSpan.GetAsciiStringEscaped(32));
+ RejectRequest(RequestRejectionReason.UnrecognizedHTTPVersion,
+ versionSpan.GetAsciiStringEscaped(32));
}
}
- break;
- case StartLineState.NewLine:
- if (ch != ByteLF)
- {
- RejectRequestLine(span);
- }
+ }
+ break;
+ case StartLineState.NewLine:
+ if (data[i] != ByteLF)
+ {
+ RejectRequestLine(span);
+ }
+ i++;
- state = StartLineState.Complete;
- break;
- case StartLineState.Complete:
- break;
- }
+ goto case StartLineState.Complete;
+ case StartLineState.Complete:
+ done = true;
+ break;
}
}
- if (state != StartLineState.Complete)
+ if (!done)
{
RejectRequestLine(span);
}
@@ -227,7 +243,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var query = span.Slice(queryStart, queryEnd - queryStart);
handler.OnStartLine(method, httpVersion, targetBuffer, pathBuffer, query, customMethod);
- consumed = buffer.Move(start, i);
+
+ consumed = end;
examined = consumed;
return true;
}
@@ -241,241 +258,256 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var bufferEnd = buffer.End;
var reader = new ReadableBufferReader(buffer);
- while (true)
+ var start = default(ReadableBufferReader);
+ var done = false;
+
+ try
{
- var start = reader;
- int ch1 = reader.Take();
- var ch2 = reader.Take();
-
- if (ch1 == -1)
+ while (!reader.End)
{
- return false;
- }
+ var span = reader.Span;
+ var remaining = span.Length;
- if (ch1 == ByteCR)
- {
- // Check for final CRLF.
- if (ch2 == -1)
+ fixed (byte* pBuffer = &span.DangerousGetPinnableReference())
{
- return false;
- }
- else if (ch2 == ByteLF)
- {
- consumed = reader.Cursor;
- examined = consumed;
- return true;
- }
-
- // Headers don't end in CRLF line.
- RejectRequest(RequestRejectionReason.HeadersCorruptedInvalidHeaderSequence);
- }
- else if (ch1 == ByteSpace || ch1 == ByteTab)
- {
- RejectRequest(RequestRejectionReason.HeaderLineMustNotStartWithWhitespace);
- }
-
- // Reset the reader since we're not at the end of headers
- reader = start;
-
- if (ReadCursorOperations.Seek(consumed, bufferEnd, out var lineEnd, ByteLF) == -1)
- {
- return false;
- }
-
- const int stackAllocLimit = 512;
-
- if (lineEnd != bufferEnd)
- {
- lineEnd = buffer.Move(lineEnd, 1);
- }
-
- var headerBuffer = buffer.Slice(consumed, lineEnd);
-
- Span span;
- if (headerBuffer.IsSingleSpan)
- {
- // No copies, directly use the one and only span
- span = headerBuffer.First.Span;
- }
- else if (headerBuffer.Length < stackAllocLimit)
- {
- // Multiple buffers and < stackAllocLimit, copy into a stack buffer
- byte* stackBuffer = stackalloc byte[headerBuffer.Length];
- span = new Span(stackBuffer, headerBuffer.Length);
- headerBuffer.CopyTo(span);
- }
- else
- {
- // We're not a single span here but we can use pooled arrays to avoid allocations in the rare case
- span = new Span(new byte[headerBuffer.Length]);
- headerBuffer.CopyTo(span);
- }
-
- var state = HeaderState.Name;
- var nameStart = 0;
- var nameEnd = -1;
- var valueStart = -1;
- var valueEnd = -1;
- var nameHasWhitespace = false;
- var previouslyWhitespace = false;
- var headerLineLength = span.Length;
-
- fixed (byte* data = &span.DangerousGetPinnableReference())
- {
- for (var i = 0; i < headerLineLength; i++)
- {
- var ch = data[i];
-
- switch (state)
+ while (remaining > 0)
{
- case HeaderState.Name:
- if (ch == ByteColon)
- {
- if (nameHasWhitespace)
- {
- RejectRequest(RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName);
- }
+ var index = reader.Index;
+ int ch1;
+ int ch2;
- state = HeaderState.Whitespace;
- nameEnd = i;
- }
-
- if (ch == ByteSpace || ch == ByteTab)
- {
- nameHasWhitespace = true;
- }
- break;
- case HeaderState.Whitespace:
+ // Fast path, we're still looking at the same span
+ if (remaining >= 2)
{
- var whitespace = ch == ByteTab || ch == ByteSpace || ch == ByteCR;
-
- if (!whitespace)
- {
- // Mark the first non whitespace char as the start of the
- // header value and change the state to expect to the header value
- valueStart = i;
- state = HeaderState.ExpectValue;
- }
- // If we see a CR then jump to the next state directly
- else if (ch == ByteCR)
- {
- state = HeaderState.ExpectValue;
- goto case HeaderState.ExpectValue;
- }
+ ch1 = pBuffer[index];
+ ch2 = pBuffer[index + 1];
}
- break;
- case HeaderState.ExpectValue:
+ else
{
- var whitespace = ch == ByteTab || ch == ByteSpace;
+ // Store the reader before we look ahead 2 bytes (probably straddling
+ // spans)
+ start = reader;
- if (whitespace)
- {
- if (!previouslyWhitespace)
- {
- // If we see a whitespace char then maybe it's end of the
- // header value
- valueEnd = i;
- }
- }
- else if (ch == ByteCR)
- {
- // If we see a CR and we haven't ever seen whitespace then
- // this is the end of the header value
- if (valueEnd == -1)
- {
- valueEnd = i;
- }
-
- // We never saw a non whitespace character before the CR
- if (valueStart == -1)
- {
- valueStart = valueEnd;
- }
-
- state = HeaderState.ExpectNewLine;
- }
- else
- {
- // If we find a non whitespace char that isn't CR then reset the end index
- valueEnd = -1;
- }
-
- previouslyWhitespace = whitespace;
+ // Possibly split across spans
+ ch1 = reader.Take();
+ ch2 = reader.Take();
}
- break;
- case HeaderState.ExpectNewLine:
- if (ch != ByteLF)
+
+ if (ch1 == ByteCR)
+ {
+ // Check for final CRLF.
+ if (ch2 == -1)
{
- RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR);
+ // Reset the reader so we don't consume anything
+ reader = start;
+ return false;
+ }
+ else if (ch2 == ByteLF)
+ {
+ // If we got 2 bytes from the span directly so skip ahead 2 so that
+ // the reader's state matches what we expect
+ if (index == reader.Index)
+ {
+ reader.Skip(2);
+ }
+
+ done = true;
+ return true;
}
- state = HeaderState.Complete;
- break;
- default:
- break;
+ // Headers don't end in CRLF line.
+ RejectRequest(RequestRejectionReason.HeadersCorruptedInvalidHeaderSequence);
+ }
+ else if(ch1 == ByteSpace || ch1 == ByteTab)
+ {
+ RejectRequest(RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName);
+ }
+
+ // We moved the reader so look ahead 2 bytes so reset both the reader
+ // and the index
+ if (index != reader.Index)
+ {
+ reader = start;
+ index = reader.Index;
+ }
+
+ var endIndex = new Span(pBuffer + index, remaining).IndexOfVectorized(ByteLF);
+ var length = 0;
+
+ if (endIndex != -1)
+ {
+ length = endIndex + 1;
+ var pHeader = pBuffer + index;
+
+ TakeSingleHeader(pHeader, length, handler);
+ }
+ else
+ {
+ var current = reader.Cursor;
+
+ // Split buffers
+ if (ReadCursorOperations.Seek(current, bufferEnd, out var lineEnd, ByteLF) == -1)
+ {
+ // Not there
+ return false;
+ }
+
+ // Make sure LF is included in lineEnd
+ lineEnd = buffer.Move(lineEnd, 1);
+ var headerSpan = buffer.Slice(current, lineEnd).ToSpan();
+ length = headerSpan.Length;
+
+ fixed (byte* pHeader = &headerSpan.DangerousGetPinnableReference())
+ {
+ TakeSingleHeader(pHeader, length, handler);
+ }
+
+ // We're going to the next span after this since we know we crossed spans here
+ // so mark the remaining as equal to the headerSpan so that we end up at 0
+ // on the next iteration
+ remaining = length;
+ }
+
+ // Skip the reader forward past the header line
+ reader.Skip(length);
+ remaining -= length;
}
}
}
- if (state == HeaderState.Name)
- {
- RejectRequest(RequestRejectionReason.NoColonCharacterFoundInHeaderLine);
- }
-
- if (state == HeaderState.ExpectValue || state == HeaderState.Whitespace)
- {
- RejectRequest(RequestRejectionReason.MissingCRInHeaderLine);
- }
-
- if (state != HeaderState.Complete)
- {
- return false;
- }
-
- // Skip the reader forward past the header line
- reader.Skip(headerLineLength);
-
- // Before accepting the header line, we need to see at least one character
- // > so we can make sure there's no space or tab
- var next = reader.Peek();
-
- // TODO: We don't need to reject the line here, we can use the state machine
- // to store the fact that we're reading a header value
- if (next == -1)
- {
- // If we can't see the next char then reject the entire line
- return false;
- }
-
- if (next == ByteSpace || next == ByteTab)
- {
- // From https://tools.ietf.org/html/rfc7230#section-3.2.4:
- //
- // Historically, HTTP header field values could be extended over
- // multiple lines by preceding each extra line with at least one space
- // or horizontal tab (obs-fold). This specification deprecates such
- // line folding except within the message/http media type
- // (Section 8.3.1). A sender MUST NOT generate a message that includes
- // line folding (i.e., that has any field-value that contains a match to
- // the obs-fold rule) unless the message is intended for packaging
- // within the message/http media type.
- //
- // A server that receives an obs-fold in a request message that is not
- // within a message/http container MUST either reject the message by
- // sending a 400 (Bad Request), preferably with a representation
- // explaining that obsolete line folding is unacceptable, or replace
- // each received obs-fold with one or more SP octets prior to
- // interpreting the field value or forwarding the message downstream.
- RejectRequest(RequestRejectionReason.HeaderValueLineFoldingNotSupported);
- }
-
- var nameBuffer = span.Slice(nameStart, nameEnd - nameStart);
- var valueBuffer = span.Slice(valueStart, valueEnd - valueStart);
- consumedBytes += headerLineLength;
-
- handler.OnHeader(nameBuffer, valueBuffer);
- consumed = reader.Cursor;
+ return false;
}
+ finally
+ {
+ consumed = reader.Cursor;
+ consumedBytes = reader.ConsumedBytes;
+
+ if (done)
+ {
+ examined = consumed;
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe int FindEndOfName(byte* headerLine, int length)
+ {
+ var index = 0;
+ var sawWhitespace = false;
+ for (; index < length; index++)
+ {
+ var ch = headerLine[index];
+ if (ch == ByteColon)
+ {
+ break;
+ }
+ if (ch == ByteTab || ch == ByteSpace || ch == ByteCR)
+ {
+ sawWhitespace = true;
+ }
+ }
+
+ if (index == length)
+ {
+ RejectRequest(RequestRejectionReason.NoColonCharacterFoundInHeaderLine);
+ }
+ if (sawWhitespace)
+ {
+ RejectRequest(RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName);
+ }
+ return index;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private unsafe void TakeSingleHeader(byte* headerLine, int length, T handler) where T : IHttpHeadersHandler
+ {
+ // Skip CR, LF from end position
+ var valueEnd = length - 3;
+ var nameEnd = FindEndOfName(headerLine, length);
+
+ if (headerLine[valueEnd + 2] != ByteLF)
+ {
+ RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR);
+ }
+ if (headerLine[valueEnd + 1] != ByteCR)
+ {
+ RejectRequest(RequestRejectionReason.MissingCRInHeaderLine);
+ }
+
+ // Skip colon from value start
+ var valueStart = nameEnd + 1;
+ // Ignore start whitespace
+ for(; valueStart < valueEnd; valueStart++)
+ {
+ var ch = headerLine[valueStart];
+ if (ch != ByteTab && ch != ByteSpace && ch != ByteCR)
+ {
+ break;
+ }
+ else if (ch == ByteCR)
+ {
+ RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR);
+ }
+ }
+
+
+ // Check for CR in value
+ var i = valueStart + 1;
+ if (Contains(headerLine + i, valueEnd - i, ByteCR))
+ {
+ RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR);
+ }
+
+ // Ignore end whitespace
+ for (; valueEnd >= valueStart; valueEnd--)
+ {
+ var ch = headerLine[valueEnd];
+ if (ch != ByteTab && ch != ByteSpace)
+ {
+ break;
+ }
+ }
+
+ var nameBuffer = new Span(headerLine, nameEnd);
+ var valueBuffer = new Span(headerLine + valueStart, valueEnd - valueStart + 1);
+
+ handler.OnHeader(nameBuffer, valueBuffer);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe bool Contains(byte* searchSpace, int length, byte value)
+ {
+ var i = 0;
+ if (Vector.IsHardwareAccelerated)
+ {
+ // Check Vector lengths
+ if (length - Vector.Count >= i)
+ {
+ var vValue = GetVector(value);
+ do
+ {
+ if (!Vector.Zero.Equals(Vector.Equals(vValue, Unsafe.Read>(searchSpace + i))))
+ {
+ goto found;
+ }
+
+ i += Vector.Count;
+ } while (length - Vector.Count >= i);
+ }
+ }
+
+ // Check remaining for CR
+ for (; i <= length; i++)
+ {
+ var ch = searchSpace[i];
+ if (ch == value)
+ {
+ goto found;
+ }
+ }
+ return false;
+ found:
+ return true;
}
private static bool IsValidTokenChar(char c)
@@ -503,31 +535,40 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
c == '~';
}
- public void RejectRequest(RequestRejectionReason reason)
+ public static void RejectRequest(RequestRejectionReason reason)
{
- RejectRequest(BadHttpRequestException.GetException(reason));
+ throw BadHttpRequestException.GetException(reason);
}
- public void RejectRequest(RequestRejectionReason reason, string value)
+ public static void RejectRequest(RequestRejectionReason reason, string value)
{
- RejectRequest(BadHttpRequestException.GetException(reason, value));
- }
-
- private void RejectRequest(BadHttpRequestException ex)
- {
- throw ex;
+ throw BadHttpRequestException.GetException(reason, value);
}
private void RejectRequestLine(Span span)
+ {
+ throw GetRejectRequestLineException(span);
+ }
+
+ private BadHttpRequestException GetRejectRequestLineException(Span span)
{
const int MaxRequestLineError = 32;
- RejectRequest(RequestRejectionReason.InvalidRequestLine,
+ return BadHttpRequestException.GetException(RequestRejectionReason.InvalidRequestLine,
Log.IsEnabled(LogLevel.Information) ? span.GetAsciiStringEscaped(MaxRequestLineError) : string.Empty);
}
public void Reset()
{
-
+
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector GetVector(byte vectorByte)
+ {
+ // Vector .ctor doesn't become an intrinsic due to detection issue
+ // However this does cause it to become an intrinsic (with additional multiply and reg->reg copy)
+ // https://github.com/dotnet/coreclr/issues/7459#issuecomment-253965670
+ return Vector.AsVectorByte(new Vector(vectorByte * 0x01010101u));
}
private enum HeaderState
@@ -551,4 +592,4 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
Complete
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/MessageBody.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/MessageBody.cs
index ce0edff4b9..8a817b82bb 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/MessageBody.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/MessageBody.cs
@@ -595,7 +595,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
try
{
- if (_context.TakeMessageHeaders(buffer, _requestHeaders, out consumed, out examined))
+ if (_context.TakeMessageHeaders(buffer, out consumed, out examined))
{
break;
}
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/PipelineExtensions.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/PipelineExtensions.cs
index c7a92e7639..1a014aff56 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/PipelineExtensions.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/PipelineExtensions.cs
@@ -3,9 +3,8 @@
using System;
using System.IO.Pipelines;
-using System.Threading;
+using System.Runtime.CompilerServices;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
@@ -47,8 +46,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
var result = await readingTask;
- await AwaitableThreadPool.Yield();
-
try
{
if (!result.Buffer.IsEmpty)
@@ -70,13 +67,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
}
- private static async Task ReadAsyncDispatchedAwaited(ReadableBufferAwaitable awaitable)
- {
- var result = await awaitable;
- await AwaitableThreadPool.Yield();
- return result;
- }
-
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span ToSpan(this ReadableBuffer buffer)
{
if (buffer.IsSingleSpan)
@@ -86,15 +77,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
return buffer.ToArray();
}
- public static ArraySegment ToArraySegment(this ReadableBuffer buffer)
- {
- if (buffer.IsSingleSpan)
- {
- return buffer.First.GetArray();
- }
- return new ArraySegment(buffer.ToArray());
- }
-
public static ArraySegment GetArray(this Memory memory)
{
ArraySegment result;
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/RequestProcessingStatus.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/RequestProcessingStatus.cs
new file mode 100644
index 0000000000..e8ebb73784
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/RequestProcessingStatus.cs
@@ -0,0 +1,14 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
+{
+ public enum RequestProcessingStatus
+ {
+ RequestPending,
+ ParsingRequestLine,
+ ParsingHeaders,
+ AppStarted,
+ ResponseStarted
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/RequestRejectionReason.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/RequestRejectionReason.cs
index 042a25d99c..b0aa5a1372 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/RequestRejectionReason.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/RequestRejectionReason.cs
@@ -7,7 +7,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
UnrecognizedHTTPVersion,
HeadersCorruptedInvalidHeaderSequence,
- HeaderLineMustNotStartWithWhitespace,
NoColonCharacterFoundInHeaderLine,
WhitespaceIsNotAllowedInHeaderName,
HeaderValueMustNotContainCR,
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/UrlPathDecoder.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/UrlPathDecoder.cs
deleted file mode 100644
index 67004cf24f..0000000000
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/UrlPathDecoder.cs
+++ /dev/null
@@ -1,312 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
-
-namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
-{
- public class UrlPathDecoder
- {
- ///
- /// Unescapes the string between given memory iterators in place.
- ///
- /// The iterator points to the beginning of the sequence.
- /// The iterator points to the byte behind the end of the sequence.
- /// The iterator points to the byte behind the end of the processed sequence.
- public static MemoryPoolIterator Unescape(MemoryPoolIterator start, MemoryPoolIterator end)
- {
- // the slot to read the input
- var reader = start;
-
- // the slot to write the unescaped byte
- var writer = reader;
-
- while (true)
- {
- if (CompareIterators(ref reader, ref end))
- {
- return writer;
- }
-
- if (reader.Peek() == '%')
- {
- var decodeReader = reader;
-
- // If decoding process succeeds, the writer iterator will be moved
- // to the next write-ready location. On the other hand if the scanned
- // percent-encodings cannot be interpreted as sequence of UTF-8 octets,
- // these bytes should be copied to output as is.
- // The decodeReader iterator is always moved to the first byte not yet
- // be scanned after the process. A failed decoding means the chars
- // between the reader and decodeReader can be copied to output untouched.
- if (!DecodeCore(ref decodeReader, ref writer, end))
- {
- Copy(reader, decodeReader, ref writer);
- }
-
- reader = decodeReader;
- }
- else
- {
- writer.Put((byte)reader.Take());
- }
- }
- }
-
- ///
- /// Unescape the percent-encodings
- ///
- /// The iterator point to the first % char
- /// The place to write to
- /// The end of the sequence
- private static bool DecodeCore(ref MemoryPoolIterator reader, ref MemoryPoolIterator writer, MemoryPoolIterator end)
- {
- // preserves the original head. if the percent-encodings cannot be interpreted as sequence of UTF-8 octets,
- // bytes from this till the last scanned one will be copied to the memory pointed by writer.
- var byte1 = UnescapePercentEncoding(ref reader, end);
-
- if (byte1 == 0)
- {
- throw BadHttpRequestException.GetException(RequestRejectionReason.PathContainsNullCharacters);
- }
-
- if (byte1 == -1)
- {
- return false;
- }
-
- if (byte1 <= 0x7F)
- {
- // first byte < U+007f, it is a single byte ASCII
- writer.Put((byte)byte1);
- return true;
- }
-
- int byte2 = 0, byte3 = 0, byte4 = 0;
-
- // anticipate more bytes
- var currentDecodeBits = 0;
- var byteCount = 1;
- var expectValueMin = 0;
- if ((byte1 & 0xE0) == 0xC0)
- {
- // 110x xxxx, expect one more byte
- currentDecodeBits = byte1 & 0x1F;
- byteCount = 2;
- expectValueMin = 0x80;
- }
- else if ((byte1 & 0xF0) == 0xE0)
- {
- // 1110 xxxx, expect two more bytes
- currentDecodeBits = byte1 & 0x0F;
- byteCount = 3;
- expectValueMin = 0x800;
- }
- else if ((byte1 & 0xF8) == 0xF0)
- {
- // 1111 0xxx, expect three more bytes
- currentDecodeBits = byte1 & 0x07;
- byteCount = 4;
- expectValueMin = 0x10000;
- }
- else
- {
- // invalid first byte
- return false;
- }
-
- var remainingBytes = byteCount - 1;
- while (remainingBytes > 0)
- {
- // read following three chars
- if (CompareIterators(ref reader, ref end))
- {
- return false;
- }
-
- var nextItr = reader;
- var nextByte = UnescapePercentEncoding(ref nextItr, end);
- if (nextByte == -1)
- {
- return false;
- }
-
- if ((nextByte & 0xC0) != 0x80)
- {
- // the follow up byte is not in form of 10xx xxxx
- return false;
- }
-
- currentDecodeBits = (currentDecodeBits << 6) | (nextByte & 0x3F);
- remainingBytes--;
-
- if (remainingBytes == 1 && currentDecodeBits >= 0x360 && currentDecodeBits <= 0x37F)
- {
- // this is going to end up in the range of 0xD800-0xDFFF UTF-16 surrogates that
- // are not allowed in UTF-8;
- return false;
- }
-
- if (remainingBytes == 2 && currentDecodeBits >= 0x110)
- {
- // this is going to be out of the upper Unicode bound 0x10FFFF.
- return false;
- }
-
- reader = nextItr;
- if (byteCount - remainingBytes == 2)
- {
- byte2 = nextByte;
- }
- else if (byteCount - remainingBytes == 3)
- {
- byte3 = nextByte;
- }
- else if (byteCount - remainingBytes == 4)
- {
- byte4 = nextByte;
- }
- }
-
- if (currentDecodeBits < expectValueMin)
- {
- // overlong encoding (e.g. using 2 bytes to encode something that only needed 1).
- return false;
- }
-
- // all bytes are verified, write to the output
- if (byteCount > 0)
- {
- writer.Put((byte)byte1);
- }
- if (byteCount > 1)
- {
- writer.Put((byte)byte2);
- }
- if (byteCount > 2)
- {
- writer.Put((byte)byte3);
- }
- if (byteCount > 3)
- {
- writer.Put((byte)byte4);
- }
-
- return true;
- }
-
- private static void Copy(MemoryPoolIterator head, MemoryPoolIterator tail, ref MemoryPoolIterator writer)
- {
- while (!CompareIterators(ref head, ref tail))
- {
- writer.Put((byte)head.Take());
- }
- }
-
- ///
- /// Read the percent-encoding and try unescape it.
- ///
- /// The operation first peek at the character the
- /// iterator points at. If it is % the is then
- /// moved on to scan the following to characters. If the two following
- /// characters are hexadecimal literals they will be unescaped and the
- /// value will be returned.
- ///
- /// If the first character is not % the iterator
- /// will be removed beyond the location of % and -1 will be returned.
- ///
- /// If the following two characters can't be successfully unescaped the
- /// iterator will be move behind the % and -1
- /// will be returned.
- ///
- /// The value to read
- /// The end of the sequence
- /// The unescaped byte if success. Otherwise return -1.
- private static int UnescapePercentEncoding(ref MemoryPoolIterator scan, MemoryPoolIterator end)
- {
- if (scan.Take() != '%')
- {
- return -1;
- }
-
- var probe = scan;
-
- int value1 = ReadHex(ref probe, end);
- if (value1 == -1)
- {
- return -1;
- }
-
- int value2 = ReadHex(ref probe, end);
- if (value2 == -1)
- {
- return -1;
- }
-
- if (SkipUnescape(value1, value2))
- {
- return -1;
- }
-
- scan = probe;
- return (value1 << 4) + value2;
- }
-
- ///
- /// Read the next char and convert it into hexadecimal value.
- ///
- /// The iterator will be moved to the next
- /// byte no matter no matter whether the operation successes.
- ///
- /// The value to read
- /// The end of the sequence
- /// The hexadecimal value if successes, otherwise -1.
- private static int ReadHex(ref MemoryPoolIterator scan, MemoryPoolIterator end)
- {
- if (CompareIterators(ref scan, ref end))
- {
- return -1;
- }
-
- var value = scan.Take();
- var isHead = (((value >= '0') && (value <= '9')) ||
- ((value >= 'A') && (value <= 'F')) ||
- ((value >= 'a') && (value <= 'f')));
-
- if (!isHead)
- {
- return -1;
- }
-
- if (value <= '9')
- {
- return value - '0';
- }
- else if (value <= 'F')
- {
- return (value - 'A') + 10;
- }
- else // a - f
- {
- return (value - 'a') + 10;
- }
- }
-
- private static bool SkipUnescape(int value1, int value2)
- {
- // skip %2F
- if (value1 == 2 && value2 == 15)
- {
- return true;
- }
-
- return false;
- }
-
- private static bool CompareIterators(ref MemoryPoolIterator lhs, ref MemoryPoolIterator rhs)
- {
- // uses ref parameter to save cost of copying
- return (lhs.Block == rhs.Block) && (lhs.Index == rhs.Index);
- }
- }
-}
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/AwaitableThreadPool.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/AwaitableThreadPool.cs
deleted file mode 100644
index 70930c73ae..0000000000
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/AwaitableThreadPool.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.Runtime.CompilerServices;
-using System.Threading.Tasks;
-
-namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
-{
- internal static class AwaitableThreadPool
- {
- internal static Awaitable Yield()
- {
- return new Awaitable();
- }
-
- internal struct Awaitable : ICriticalNotifyCompletion
- {
- public void GetResult()
- {
-
- }
-
- public Awaitable GetAwaiter() => this;
-
- public bool IsCompleted => false;
-
- public void OnCompleted(Action continuation)
- {
- Task.Run(continuation);
- }
-
- public void UnsafeOnCompleted(Action continuation)
- {
- OnCompleted(continuation);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/Constants.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/Constants.cs
index e0f592154b..2c201fea7c 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/Constants.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/Constants.cs
@@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
///
/// The IPEndPoint Kestrel will bind to if nothing else is specified.
///
- public static readonly IPEndPoint DefaultIPEndPoint = new IPEndPoint(IPAddress.Loopback, 5000);
+ public static readonly string DefaultServerAddress = "http://localhost:5000";
///
/// Prefix of host name used to specify Unix sockets in the configuration.
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/HttpUtilities.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/HttpUtilities.cs
index ca765963ba..da9719a841 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/HttpUtilities.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/HttpUtilities.cs
@@ -124,6 +124,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
}
}
+ public unsafe static string GetAsciiStringNonNullCharacters(this Span span)
+ {
+ if (span.IsEmpty)
+ {
+ return string.Empty;
+ }
+
+ var asciiString = new string('\0', span.Length);
+
+ fixed (char* output = asciiString)
+ fixed (byte* buffer = &span.DangerousGetPinnableReference())
+ {
+ // This version if AsciiUtilities returns null if there are any null (0 byte) characters
+ // in the string
+ if (!AsciiUtilities.TryGetAsciiString(buffer, output, span.Length))
+ {
+ throw new InvalidOperationException();
+ }
+ }
+ return asciiString;
+ }
+
public static string GetAsciiStringEscaped(this Span span, int maxChars)
{
var sb = new StringBuilder();
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIterator.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIterator.cs
index 07491b7377..0f4d3cebe5 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIterator.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIterator.cs
@@ -14,15 +14,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
public struct MemoryPoolIterator
{
private const int _maxULongByteLength = 20;
- private const ulong _xorPowerOfTwoToHighByte = (0x07ul |
- 0x06ul << 8 |
- 0x05ul << 16 |
- 0x04ul << 24 |
- 0x03ul << 32 |
- 0x02ul << 40 |
- 0x01ul << 48 ) + 1;
-
- private static readonly int _vectorSpan = Vector.Count;
[ThreadStatic]
private static byte[] _numericBytesScratch;
@@ -135,668 +126,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
}
} while (true);
}
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Skip(int bytesToSkip)
- {
- var block = _block;
- if (block == null && bytesToSkip > 0)
- {
- ThrowInvalidOperationException_SkipMoreThanAvailable();
- }
-
- // Always set wasLastBlock before checking .End to avoid race which may cause data loss
- var wasLastBlock = block.Next == null;
- var following = block.End - _index;
-
- if (following >= bytesToSkip)
- {
- _index += bytesToSkip;
- return;
- }
-
- if (wasLastBlock)
- {
- ThrowInvalidOperationException_SkipMoreThanAvailable();
- }
-
- SkipMultiBlock(bytesToSkip, following);
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private void SkipMultiBlock(int bytesToSkip, int following)
- {
- var block = _block;
- do
- {
- bytesToSkip -= following;
- block = block.Next;
- var index = block.Start;
-
- // Always set wasLastBlock before checking .End to avoid race which may cause data loss
- var wasLastBlock = block.Next == null;
- following = block.End - index;
-
- if (following >= bytesToSkip)
- {
- _block = block;
- _index = index + bytesToSkip;
- return;
- }
-
- if (wasLastBlock)
- {
- ThrowInvalidOperationException_SkipMoreThanAvailable();
- }
- } while (true);
- }
-
- private static void ThrowInvalidOperationException_SkipMoreThanAvailable()
- {
- throw new InvalidOperationException("Attempted to skip more bytes than available.");
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public int Peek()
- {
- var block = _block;
- if (block == null)
- {
- return -1;
- }
-
- var index = _index;
-
- // Always set wasLastBlock before checking .End to avoid race which may cause data loss
- var wasLastBlock = block.Next == null;
- if (index < block.End)
- {
- return block.Array[index];
- }
-
- return wasLastBlock ? -1 : PeekMultiBlock();
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private int PeekMultiBlock()
- {
- var block = _block;
- do
- {
- block = block.Next;
- var index = block.Start;
-
- // Always set wasLastBlock before checking .End to avoid race which may cause data loss
- var wasLastBlock = block.Next == null;
-
- if (index < block.End)
- {
- return block.Array[index];
- }
- if (wasLastBlock)
- {
- return -1;
- }
- } while (true);
- }
-
- // NOTE: Little-endian only!
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public unsafe bool TryPeekLong(out ulong longValue)
- {
- longValue = 0;
-
- var block = _block;
- if (block == null)
- {
- return false;
- }
-
- // Always set wasLastBlock before checking .End to avoid race which may cause data loss
- var wasLastBlock = block.Next == null;
- var blockBytes = block.End - _index;
-
- if (blockBytes >= sizeof(ulong))
- {
- longValue = *(ulong*)(block.DataFixedPtr + _index);
- return true;
- }
-
- return wasLastBlock ? false : TryPeekLongMultiBlock(ref longValue, blockBytes);
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private unsafe bool TryPeekLongMultiBlock(ref ulong longValue, int blockBytes)
- {
- // Each block will be filled with at least 2048 bytes before the Next pointer is set, so a long
- // will cross at most one block boundary assuming there are at least 8 bytes following the iterator.
- var nextBytes = sizeof(ulong) - blockBytes;
-
- var block = _block;
- if (block.Next.End - block.Next.Start < nextBytes)
- {
- return false;
- }
-
- var nextLong = *(ulong*)(block.Next.DataFixedPtr + block.Next.Start);
-
- if (blockBytes == 0)
- {
- // This case can not fall through to the else block since that would cause a 64-bit right shift
- // on blockLong which is equivalent to no shift at all instead of shifting in all zeros.
- // https://msdn.microsoft.com/en-us/library/xt18et0d.aspx
- longValue = nextLong;
- }
- else
- {
- var blockLong = *(ulong*)(block.DataFixedPtr + block.End - sizeof(ulong));
-
- // Ensure that the right shift has a ulong operand so a logical shift is performed.
- longValue = (blockLong >> nextBytes * 8) | (nextLong << blockBytes * 8);
- }
-
- return true;
- }
-
- public int Seek(byte byte0)
- {
- int bytesScanned;
- return Seek(byte0, out bytesScanned);
- }
-
- public unsafe int Seek(
- byte byte0,
- out int bytesScanned,
- int limit = int.MaxValue)
- {
- bytesScanned = 0;
-
- var block = _block;
- if (block == null || limit <= 0)
- {
- return -1;
- }
-
- var index = _index;
- var wasLastBlock = block.Next == null;
- var following = block.End - index;
- byte[] array;
- var byte0Vector = GetVector(byte0);
-
- while (true)
- {
- while (following == 0)
- {
- if (bytesScanned >= limit || wasLastBlock)
- {
- _block = block;
- _index = index;
- return -1;
- }
-
- block = block.Next;
- index = block.Start;
- wasLastBlock = block.Next == null;
- following = block.End - index;
- }
- array = block.Array;
- while (following > 0)
- {
- // Need unit tests to test Vector path
-#if !DEBUG
- // Check will be Jitted away https://github.com/dotnet/coreclr/issues/1079
- if (Vector.IsHardwareAccelerated)
- {
-#endif
- if (following >= _vectorSpan)
- {
- var byte0Equals = Vector.Equals(new Vector(array, index), byte0Vector);
-
- if (byte0Equals.Equals(Vector.Zero))
- {
- if (bytesScanned + _vectorSpan >= limit)
- {
- _block = block;
- // Ensure iterator is left at limit position
- _index = index + (limit - bytesScanned);
- bytesScanned = limit;
- return -1;
- }
-
- bytesScanned += _vectorSpan;
- following -= _vectorSpan;
- index += _vectorSpan;
- continue;
- }
-
- _block = block;
-
- var firstEqualByteIndex = LocateFirstFoundByte(byte0Equals);
- var vectorBytesScanned = firstEqualByteIndex + 1;
-
- if (bytesScanned + vectorBytesScanned > limit)
- {
- // Ensure iterator is left at limit position
- _index = index + (limit - bytesScanned);
- bytesScanned = limit;
- return -1;
- }
-
- _index = index + firstEqualByteIndex;
- bytesScanned += vectorBytesScanned;
-
- return byte0;
- }
- // Need unit tests to test Vector path
-#if !DEBUG
- }
-#endif
-
- var pCurrent = (block.DataFixedPtr + index);
- var pEnd = pCurrent + Math.Min(following, limit - bytesScanned);
- do
- {
- bytesScanned++;
- if (*pCurrent == byte0)
- {
- _block = block;
- _index = index;
- return byte0;
- }
- pCurrent++;
- index++;
- } while (pCurrent < pEnd);
-
- following = 0;
- break;
- }
- }
- }
-
- public unsafe int Seek(
- byte byte0,
- ref MemoryPoolIterator limit)
- {
- var block = _block;
- if (block == null)
- {
- return -1;
- }
-
- var index = _index;
- var wasLastBlock = block.Next == null;
- var following = block.End - index;
-
- while (true)
- {
- while (following == 0)
- {
- if ((block == limit.Block && index > limit.Index) ||
- wasLastBlock)
- {
- _block = block;
- // Ensure iterator is left at limit position
- _index = limit.Index;
- return -1;
- }
-
- block = block.Next;
- index = block.Start;
- wasLastBlock = block.Next == null;
- following = block.End - index;
- }
- var array = block.Array;
- while (following > 0)
- {
-// Need unit tests to test Vector path
-#if !DEBUG
- // Check will be Jitted away https://github.com/dotnet/coreclr/issues/1079
- if (Vector.IsHardwareAccelerated)
- {
-#endif
- if (following >= _vectorSpan)
- {
- var byte0Equals = Vector.Equals(new Vector(array, index), GetVector(byte0));
-
- if (byte0Equals.Equals(Vector.Zero))
- {
- if (block == limit.Block && index + _vectorSpan > limit.Index)
- {
- _block = block;
- // Ensure iterator is left at limit position
- _index = limit.Index;
- return -1;
- }
-
- following -= _vectorSpan;
- index += _vectorSpan;
- continue;
- }
-
- _block = block;
-
- var firstEqualByteIndex = LocateFirstFoundByte(byte0Equals);
-
- if (_block == limit.Block && index + firstEqualByteIndex > limit.Index)
- {
- // Ensure iterator is left at limit position
- _index = limit.Index;
- return -1;
- }
-
- _index = index + firstEqualByteIndex;
-
- return byte0;
- }
-// Need unit tests to test Vector path
-#if !DEBUG
- }
-#endif
-
- var pCurrent = (block.DataFixedPtr + index);
- var pEnd = block == limit.Block ? block.DataFixedPtr + limit.Index + 1 : pCurrent + following;
- do
- {
- if (*pCurrent == byte0)
- {
- _block = block;
- _index = index;
- return byte0;
- }
- pCurrent++;
- index++;
- } while (pCurrent < pEnd);
-
- following = 0;
- break;
- }
- }
- }
-
- public int Seek(byte byte0, byte byte1)
- {
- var limit = new MemoryPoolIterator();
- return Seek(byte0, byte1, ref limit);
- }
-
- public unsafe int Seek(
- byte byte0,
- byte byte1,
- ref MemoryPoolIterator limit)
- {
- var block = _block;
- if (block == null)
- {
- return -1;
- }
-
- var index = _index;
- var wasLastBlock = block.Next == null;
- var following = block.End - index;
- int byteIndex = int.MaxValue;
-
- while (true)
- {
- while (following == 0)
- {
- if ((block == limit.Block && index > limit.Index) ||
- wasLastBlock)
- {
- _block = block;
- // Ensure iterator is left at limit position
- _index = limit.Index;
- return -1;
- }
- block = block.Next;
- index = block.Start;
- wasLastBlock = block.Next == null;
- following = block.End - index;
- }
- var array = block.Array;
- while (following > 0)
- {
-
-// Need unit tests to test Vector path
-#if !DEBUG
- // Check will be Jitted away https://github.com/dotnet/coreclr/issues/1079
- if (Vector.IsHardwareAccelerated)
- {
-#endif
- if (following >= _vectorSpan)
- {
- var data = new Vector(array, index);
-
- var byteEquals = Vector.Equals(data, GetVector(byte0));
- byteEquals = Vector.ConditionalSelect(byteEquals, byteEquals, Vector.Equals(data, GetVector(byte1)));
-
- if (!byteEquals.Equals(Vector.Zero))
- {
- byteIndex = LocateFirstFoundByte(byteEquals);
- }
-
- if (byteIndex == int.MaxValue)
- {
- following -= _vectorSpan;
- index += _vectorSpan;
-
- if (block == limit.Block && index > limit.Index)
- {
- _block = block;
- // Ensure iterator is left at limit position
- _index = limit.Index;
- return -1;
- }
-
- continue;
- }
-
- _block = block;
-
- _index = index + byteIndex;
-
- if (block == limit.Block && _index > limit.Index)
- {
- // Ensure iterator is left at limit position
- _index = limit.Index;
- return -1;
- }
-
- _index = index + byteIndex;
-
- if (block == limit.Block && _index > limit.Index)
- {
- // Ensure iterator is left at limit position
- _index = limit.Index;
- return -1;
- }
-
- return block.Array[index + byteIndex];
- }
-// Need unit tests to test Vector path
-#if !DEBUG
- }
-#endif
- var pCurrent = (block.DataFixedPtr + index);
- var pEnd = block == limit.Block ? block.DataFixedPtr + limit.Index + 1 : pCurrent + following;
- do
- {
- if (*pCurrent == byte0)
- {
- _block = block;
- _index = index;
- return byte0;
- }
- if (*pCurrent == byte1)
- {
- _block = block;
- _index = index;
- return byte1;
- }
- pCurrent++;
- index++;
- } while (pCurrent != pEnd);
-
- following = 0;
- break;
- }
- }
- }
-
- public int Seek(byte byte0, byte byte1, byte byte2)
- {
- var limit = new MemoryPoolIterator();
- return Seek(byte0, byte1, byte2, ref limit);
- }
-
- public unsafe int Seek(
- byte byte0,
- byte byte1,
- byte byte2,
- ref MemoryPoolIterator limit)
- {
- var block = _block;
- if (block == null)
- {
- return -1;
- }
-
- var index = _index;
- var wasLastBlock = block.Next == null;
- var following = block.End - index;
- int byteIndex = int.MaxValue;
-
- while (true)
- {
- while (following == 0)
- {
- if ((block == limit.Block && index > limit.Index) ||
- wasLastBlock)
- {
- _block = block;
- // Ensure iterator is left at limit position
- _index = limit.Index;
- return -1;
- }
- block = block.Next;
- index = block.Start;
- wasLastBlock = block.Next == null;
- following = block.End - index;
- }
- var array = block.Array;
- while (following > 0)
- {
-// Need unit tests to test Vector path
-#if !DEBUG
- // Check will be Jitted away https://github.com/dotnet/coreclr/issues/1079
- if (Vector.IsHardwareAccelerated)
- {
-#endif
- if (following >= _vectorSpan)
- {
- var data = new Vector(array, index);
-
- var byteEquals = Vector.Equals(data, GetVector(byte0));
- byteEquals = Vector.ConditionalSelect(byteEquals, byteEquals, Vector.Equals(data, GetVector(byte1)));
- byteEquals = Vector.ConditionalSelect(byteEquals, byteEquals, Vector.Equals(data, GetVector(byte2)));
-
- if (!byteEquals.Equals(Vector.Zero))
- {
- byteIndex = LocateFirstFoundByte(byteEquals);
- }
-
- if (byteIndex == int.MaxValue)
- {
- following -= _vectorSpan;
- index += _vectorSpan;
-
- if (block == limit.Block && index > limit.Index)
- {
- _block = block;
- // Ensure iterator is left at limit position
- _index = limit.Index;
- return -1;
- }
-
- continue;
- }
-
- _block = block;
-
- _index = index + byteIndex;
-
- if (block == limit.Block && _index > limit.Index)
- {
- // Ensure iterator is left at limit position
- _index = limit.Index;
- return -1;
- }
-
- return block.Array[index + byteIndex];
- }
-// Need unit tests to test Vector path
-#if !DEBUG
- }
-#endif
- var pCurrent = (block.DataFixedPtr + index);
- var pEnd = block == limit.Block ? block.DataFixedPtr + limit.Index + 1 : pCurrent + following;
- do
- {
- if (*pCurrent == byte0)
- {
- _block = block;
- _index = index;
- return byte0;
- }
- if (*pCurrent == byte1)
- {
- _block = block;
- _index = index;
- return byte1;
- }
- if (*pCurrent == byte2)
- {
- _block = block;
- _index = index;
- return byte2;
- }
- pCurrent++;
- index++;
- } while (pCurrent != pEnd);
-
- following = 0;
- break;
- }
- }
- }
-
- ///
- /// Locate the first of the found bytes
- ///
- ///
- /// The first index of the result vector
- // Force inlining (64 IL bytes, 91 bytes asm) Issue: https://github.com/dotnet/coreclr/issues/7386
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static int LocateFirstFoundByte(Vector byteEquals)
- {
- var vector64 = Vector.AsVectorUInt64(byteEquals);
- ulong longValue = 0;
- var i = 0;
- // Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001
- for (; i < Vector.Count; i++)
- {
- longValue = vector64[i];
- if (longValue == 0) continue;
- break;
- }
-
- // Flag least significant power of two bit
- var powerOfTwoFlag = (longValue ^ (longValue - 1));
- // Shift all powers of two into the high byte and extract
- var foundByteIndex = (int)((powerOfTwoFlag * _xorPowerOfTwoToHighByte) >> 57);
- // Single LEA instruction with jitted const (using function result)
- return i * 8 + foundByteIndex;
- }
-
+
///
/// Save the data at the current location then move to the next available space.
///
@@ -914,57 +244,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
}
}
- public MemoryPoolIterator CopyTo(byte[] array, int offset, int count, out int actual)
- {
- var block = _block;
- if (block == null)
- {
- actual = 0;
- return this;
- }
-
- var index = _index;
- var remaining = count;
- while (true)
- {
- // Determine if we might attempt to copy data from block.Next before
- // calculating "following" so we don't risk skipping data that could
- // be added after block.End when we decide to copy from block.Next.
- // block.End will always be advanced before block.Next is set.
- var wasLastBlock = block.Next == null;
- var following = block.End - index;
- if (remaining <= following)
- {
- actual = count;
- if (array != null)
- {
- Buffer.BlockCopy(block.Array, index, array, offset, remaining);
- }
- return new MemoryPoolIterator(block, index + remaining);
- }
- else if (wasLastBlock)
- {
- actual = count - remaining + following;
- if (array != null)
- {
- Buffer.BlockCopy(block.Array, index, array, offset, following);
- }
- return new MemoryPoolIterator(block, index + following);
- }
- else
- {
- if (array != null)
- {
- Buffer.BlockCopy(block.Array, index, array, offset, following);
- }
- offset += following;
- remaining -= following;
- block = block.Next;
- index = block.Start;
- }
- }
- }
-
public void CopyFrom(byte[] data)
{
CopyFrom(data, 0, data.Length);
@@ -1180,177 +459,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
CopyFrom(byteBuffer, position, _maxULongByteLength - position);
}
-
- public unsafe string GetAsciiString(ref MemoryPoolIterator end)
- {
- var block = _block;
- if (block == null || end.IsDefault)
- {
- return null;
- }
-
- var length = GetLength(end);
-
- if (length == 0)
- {
- return null;
- }
-
- var inputOffset = _index;
-
- var asciiString = new string('\0', length);
-
- fixed (char* outputStart = asciiString)
- {
- var output = outputStart;
- var remaining = length;
-
- var endBlock = end.Block;
- var endIndex = end.Index;
-
- var outputOffset = 0;
- while (true)
- {
- int following = (block != endBlock ? block.End : endIndex) - inputOffset;
-
- if (following > 0)
- {
- if (!AsciiUtilities.TryGetAsciiString(block.DataFixedPtr + inputOffset, output + outputOffset, following))
- {
- throw BadHttpRequestException.GetException(RequestRejectionReason.NonAsciiOrNullCharactersInInputString);
- }
-
- outputOffset += following;
- remaining -= following;
- }
-
- if (remaining == 0)
- {
- break;
- }
-
- block = block.Next;
- inputOffset = block.Start;
- }
- }
-
- return asciiString;
- }
-
- public string GetUtf8String(ref MemoryPoolIterator end)
- {
- var block = _block;
- if (block == null || end.IsDefault)
- {
- return default(string);
- }
-
- var index = _index;
- if (end.Block == block)
- {
- return Encoding.UTF8.GetString(block.Array, index, end.Index - index);
- }
-
- var decoder = Encoding.UTF8.GetDecoder();
-
- var length = GetLength(end);
- var charLength = length;
- // Worse case is 1 byte = 1 char
- var chars = new char[charLength];
- var charIndex = 0;
-
- var remaining = length;
- while (true)
- {
- int bytesUsed;
- int charsUsed;
- bool completed;
- var following = block.End - index;
- if (remaining <= following)
- {
- decoder.Convert(
- block.Array,
- index,
- remaining,
- chars,
- charIndex,
- charLength - charIndex,
- true,
- out bytesUsed,
- out charsUsed,
- out completed);
- return new string(chars, 0, charIndex + charsUsed);
- }
- else if (block.Next == null)
- {
- decoder.Convert(
- block.Array,
- index,
- following,
- chars,
- charIndex,
- charLength - charIndex,
- true,
- out bytesUsed,
- out charsUsed,
- out completed);
- return new string(chars, 0, charIndex + charsUsed);
- }
- else
- {
- decoder.Convert(
- block.Array,
- index,
- following,
- chars,
- charIndex,
- charLength - charIndex,
- false,
- out bytesUsed,
- out charsUsed,
- out completed);
- charIndex += charsUsed;
- remaining -= following;
- block = block.Next;
- index = block.Start;
- }
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ArraySegment GetArraySegment(MemoryPoolIterator end)
- {
- var block = _block;
- if (block == null || end.IsDefault)
- {
- return default(ArraySegment);
- }
-
- var index = _index;
- if (end.Block == block)
- {
- return new ArraySegment(block.Array, index, end.Index - index);
- }
-
- return GetArraySegmentMultiBlock(ref end);
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private ArraySegment GetArraySegmentMultiBlock(ref MemoryPoolIterator end)
- {
- var length = GetLength(end);
- var array = new byte[length];
- CopyTo(array, 0, length, out length);
- return new ArraySegment(array, 0, length);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Vector GetVector(byte vectorByte)
- {
- // Vector .ctor doesn't become an intrinsic due to detection issue
- // However this does cause it to become an intrinsic (with additional multiply and reg->reg copy)
- // https://github.com/dotnet/coreclr/issues/7459#issuecomment-253965670
- return Vector.AsVectorByte(new Vector(vectorByte * 0x01010101u));
- }
}
}
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServer.cs b/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServer.cs
index 1d05a0dd2b..a36da353f4 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServer.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServer.cs
@@ -142,8 +142,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel
}
else if (!hasListenOptions && !hasServerAddresses)
{
- _logger.LogDebug($"No listening endpoints were configured. Binding to {Constants.DefaultIPEndPoint} by default.");
- listenOptions.Add(new ListenOptions(Constants.DefaultIPEndPoint));
+ _logger.LogDebug($"No listening endpoints were configured. Binding to {Constants.DefaultServerAddress} by default.");
+
+ // "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
+ StartLocalhost(engine, ServerAddress.FromUrl(Constants.DefaultServerAddress));
+
+ // If StartLocalhost doesn't throw, there is at least one listener.
+ // The port cannot change for "localhost".
+ _serverAddresses.Addresses.Add(Constants.DefaultServerAddress);
+
+ return;
}
else if (!hasListenOptions)
{
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Microsoft.AspNetCore.Server.Kestrel.csproj b/src/Microsoft.AspNetCore.Server.Kestrel/Microsoft.AspNetCore.Server.Kestrel.csproj
index 5dd7b984c9..35ccda46b3 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Microsoft.AspNetCore.Server.Kestrel.csproj
+++ b/src/Microsoft.AspNetCore.Server.Kestrel/Microsoft.AspNetCore.Server.Kestrel.csproj
@@ -18,10 +18,9 @@
-
-
-
-
+
+
+
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs
index 00bd4a0826..572b45aa68 100644
--- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs
@@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
+using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.DependencyInjection;
@@ -129,31 +130,45 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
- [ConditionalFact(Skip = "Waiting on https://github.com/aspnet/Hosting/issues/917")]
+ [ConditionalFact]
[PortSupportedCondition(5000)]
- public async Task DefaultsToPort5000()
+ public Task DefaultsServerAddress_BindsToIPv4()
+ {
+ return RegisterDefaultServerAddresses_Success(new[] { "http://127.0.0.1:5000" });
+ }
+
+ [ConditionalFact]
+ [IPv6SupportedCondition]
+ [PortSupportedCondition(5000)]
+ public Task DefaultsServerAddress_BindsToIPv6()
+ {
+ return RegisterDefaultServerAddresses_Success(new[] { "http://127.0.0.1:5000", "http://[::1]:5000" });
+ }
+
+ private async Task RegisterDefaultServerAddresses_Success(IEnumerable addresses)
{
var testLogger = new TestApplicationErrorLogger();
var hostBuilder = new WebHostBuilder()
.UseKestrel()
.ConfigureServices(services =>
- {
- services.AddSingleton(new KestrelTestLoggerFactory(testLogger));
- })
+ {
+ services.AddSingleton(new KestrelTestLoggerFactory(testLogger));
+ })
.Configure(ConfigureEchoAddress);
using (var host = hostBuilder.Build())
{
host.Start();
- var debugLog = testLogger.Messages.Single(log => log.LogLevel == LogLevel.Debug);
- Assert.True(debugLog.Message.Contains("default"));
+ Assert.Equal(5000, host.GetPort());
+ Assert.Single(testLogger.Messages, log => log.LogLevel == LogLevel.Debug &&
+ string.Equals($"No listening endpoints were configured. Binding to {Constants.DefaultServerAddress} by default.",
+ log.Message, StringComparison.Ordinal));
- foreach (var testUrl in new[] { "http://127.0.0.1:5000", "http://localhost:5000" })
+ foreach (var address in addresses)
{
- var response = await HttpClientSlim.GetStringAsync(testUrl);
- Assert.Equal(new Uri(testUrl).ToString(), response);
+ Assert.Equal(new Uri(address).ToString(), await HttpClientSlim.GetStringAsync(address));
}
}
}
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/BadHttpRequestTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/BadHttpRequestTests.cs
index 0394b7fcd3..565b06c2f1 100644
--- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/BadHttpRequestTests.cs
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/BadHttpRequestTests.cs
@@ -2,10 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
-using System.Linq;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Internal;
+using Microsoft.Extensions.Logging;
+using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
@@ -14,106 +16,102 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
[Theory]
[MemberData(nameof(InvalidRequestLineData))]
- public async Task TestInvalidRequestLines(string request)
+ public Task TestInvalidRequestLines(string request, string expectedExceptionMessage)
{
- using (var server = new TestServer(context => TaskCache.CompletedTask))
- {
- using (var connection = server.CreateConnection())
- {
- await connection.SendAll(request);
- await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
- }
- }
+ return TestBadRequest(
+ request,
+ "400 Bad Request",
+ expectedExceptionMessage);
}
[Theory]
[MemberData(nameof(UnrecognizedHttpVersionData))]
- public async Task TestInvalidRequestLinesWithUnrecognizedVersion(string httpVersion)
+ public Task TestInvalidRequestLinesWithUnrecognizedVersion(string httpVersion)
{
- using (var server = new TestServer(context => TaskCache.CompletedTask))
- {
- using (var connection = server.CreateConnection())
- {
- await connection.SendAll($"GET / {httpVersion}\r\n");
- await ReceiveBadRequestResponse(connection, "505 HTTP Version Not Supported", server.Context.DateHeaderValue);
- }
- }
+ return TestBadRequest(
+ $"GET / {httpVersion}\r\n",
+ "505 HTTP Version Not Supported",
+ $"Unrecognized HTTP version: {httpVersion}");
}
[Theory]
[MemberData(nameof(InvalidRequestHeaderData))]
- public async Task TestInvalidHeaders(string rawHeaders)
+ public Task TestInvalidHeaders(string rawHeaders, string expectedExceptionMessage)
{
- using (var server = new TestServer(context => TaskCache.CompletedTask))
- {
- using (var connection = server.CreateConnection())
- {
- await connection.SendAll($"GET / HTTP/1.1\r\n{rawHeaders}");
- await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
- }
- }
+ return TestBadRequest(
+ $"GET / HTTP/1.1\r\n{rawHeaders}",
+ "400 Bad Request",
+ expectedExceptionMessage);
}
- [Fact]
- public async Task BadRequestWhenHeaderNameContainsNonASCIICharacters()
+ [Theory]
+ [InlineData("Hea\0der: value", "Invalid characters in header name.")]
+ [InlineData("Header: va\0lue", "Malformed request: invalid headers.")]
+ [InlineData("Head\x80r: value", "Invalid characters in header name.")]
+ [InlineData("Header: valu\x80", "Malformed request: invalid headers.")]
+ public Task BadRequestWhenHeaderNameContainsNonASCIIOrNullCharacters(string header, string expectedExceptionMessage)
{
- using (var server = new TestServer(context => { return Task.FromResult(0); }))
- {
- using (var connection = server.CreateConnection())
- {
- await connection.SendAll(
- "GET / HTTP/1.1",
- "H\u00eb\u00e4d\u00ebr: value",
- "",
- "");
- await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
- }
- }
+ return TestBadRequest(
+ $"GET / HTTP/1.1\r\n{header}\r\n\r\n",
+ "400 Bad Request",
+ expectedExceptionMessage);
}
[Theory]
[InlineData("POST")]
[InlineData("PUT")]
- public async Task BadRequestIfMethodRequiresLengthButNoContentLengthOrTransferEncodingInRequest(string method)
+ public Task BadRequestIfMethodRequiresLengthButNoContentLengthOrTransferEncodingInRequest(string method)
{
- using (var server = new TestServer(context => { return Task.FromResult(0); }))
- {
- using (var connection = server.CreateConnection())
- {
- await connection.Send($"{method} / HTTP/1.1\r\n\r\n");
- await ReceiveBadRequestResponse(connection, "411 Length Required", server.Context.DateHeaderValue);
- }
- }
+ return TestBadRequest(
+ $"{method} / HTTP/1.1\r\n\r\n",
+ "411 Length Required",
+ $"{method} request contains no Content-Length or Transfer-Encoding header");
}
[Theory]
[InlineData("POST")]
[InlineData("PUT")]
- public async Task BadRequestIfMethodRequiresLengthButNoContentLengthInHttp10Request(string method)
+ public Task BadRequestIfMethodRequiresLengthButNoContentLengthInHttp10Request(string method)
{
- using (var server = new TestServer(context => { return Task.FromResult(0); }))
- {
- using (var connection = server.CreateConnection())
- {
- await connection.Send($"{method} / HTTP/1.0\r\n\r\n");
- await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
- }
- }
+ return TestBadRequest(
+ $"{method} / HTTP/1.0\r\n\r\n",
+ "400 Bad Request",
+ $"{method} request contains no Content-Length header");
}
[Theory]
[InlineData("NaN")]
[InlineData("-1")]
- public async Task BadRequestIfContentLengthInvalid(string contentLength)
+ public Task BadRequestIfContentLengthInvalid(string contentLength)
{
- using (var server = new TestServer(context => { return Task.FromResult(0); }))
+ return TestBadRequest(
+ $"POST / HTTP/1.1\r\nContent-Length: {contentLength}\r\n\r\n",
+ "400 Bad Request",
+ $"Invalid content length: {contentLength}");
+ }
+
+ private async Task TestBadRequest(string request, string expectedResponseStatusCode, string expectedExceptionMessage)
+ {
+ BadHttpRequestException loggedException = null;
+ var mockKestrelTrace = new Mock();
+ mockKestrelTrace
+ .Setup(trace => trace.IsEnabled(LogLevel.Information))
+ .Returns(true);
+ mockKestrelTrace
+ .Setup(trace => trace.ConnectionBadRequest(It.IsAny(), It.IsAny()))
+ .Callback((connectionId, exception) => loggedException = exception);
+
+ using (var server = new TestServer(context => TaskCache.CompletedTask, new TestServiceContext { Log = mockKestrelTrace.Object }))
{
using (var connection = server.CreateConnection())
{
- await connection.SendAll($"GET / HTTP/1.1\r\nContent-Length: {contentLength}\r\n\r\n");
- await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
+ await connection.SendAll(request);
+ await ReceiveBadRequestResponse(connection, expectedResponseStatusCode, server.Context.DateHeaderValue);
}
}
+
+ mockKestrelTrace.Verify(trace => trace.ConnectionBadRequest(It.IsAny(), It.IsAny()));
+ Assert.Equal(expectedExceptionMessage, loggedException.Message);
}
private async Task ReceiveBadRequestResponse(TestConnection connection, string expectedResponseStatusCode, string expectedDateHeaderValue)
@@ -127,10 +125,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
"");
}
- public static IEnumerable
-
+
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/Program.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/Program.cs
index e6bea9e92c..95ca5909db 100644
--- a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/Program.cs
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/Program.cs
@@ -40,10 +40,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
BenchmarkRunner.Run();
}
- if (type.HasFlag(BenchmarkType.KnownStrings))
+ if (type.HasFlag(BenchmarkType.KnownStrings))
{
BenchmarkRunner.Run();
}
+ if (type.HasFlag(BenchmarkType.KestrelHttpParser))
+ {
+ BenchmarkRunner.Run();
+ }
+ if (type.HasFlag(BenchmarkType.FrameParsingOverhead))
+ {
+ BenchmarkRunner.Run();
+ }
}
}
@@ -54,6 +62,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
Writing = 2,
Throughput = 4,
KnownStrings = 8,
+ KestrelHttpParser = 16,
+ FrameParsingOverhead = 32,
// add new ones in powers of two - e.g. 2,4,8,16...
All = uint.MaxValue
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/RequestParsing.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/RequestParsing.cs
index 9d91d8e681..8fc4bc0eea 100644
--- a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/RequestParsing.cs
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/RequestParsing.cs
@@ -3,128 +3,101 @@
using System;
using System.IO.Pipelines;
-using System.Linq;
-using System.Text;
using BenchmarkDotNet.Attributes;
-using Microsoft.AspNetCore.Server.Kestrel.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
-using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing;
-using MemoryPool = Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure.MemoryPool;
-using RequestLineStatus = Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame.RequestLineStatus;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
[Config(typeof(CoreConfig))]
public class RequestParsing
{
- private const int InnerLoopCount = 512;
- private const int Pipelining = 16;
-
- private const string plaintextRequest = "GET /plaintext HTTP/1.1\r\nHost: www.example.com\r\n\r\n";
-
- private const string liveaspnetRequest = "GET https://live.asp.net/ HTTP/1.1\r\n" +
- "Host: live.asp.net\r\n" +
- "Connection: keep-alive\r\n" +
- "Upgrade-Insecure-Requests: 1\r\n" +
- "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36\r\n" +
- "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n" +
- "DNT: 1\r\n" +
- "Accept-Encoding: gzip, deflate, sdch, br\r\n" +
- "Accept-Language: en-US,en;q=0.8\r\n" +
- "Cookie: __unam=7a67379-1s65dc575c4-6d778abe-1; omniID=9519gfde_3347_4762_8762_df51458c8ec2\r\n\r\n";
-
- private const string unicodeRequest =
- "GET http://stackoverflow.com/questions/40148683/why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric HTTP/1.1\r\n" +
- "Accept: text/html, application/xhtml+xml, image/jxr, */*\r\n" +
- "Accept-Language: en-US,en-GB;q=0.7,en;q=0.3\r\n" +
- "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.14965\r\n" +
- "Accept-Encoding: gzip, deflate\r\n" +
- "Host: stackoverflow.com\r\n" +
- "Connection: Keep-Alive\r\n" +
- "Cache-Control: max-age=0\r\n" +
- "Upgrade-Insecure-Requests: 1\r\n" +
- "DNT: 1\r\n" +
- "Referer: http://stackoverflow.com/?tab=month\r\n" +
- "Pragma: no-cache\r\n" +
- "Cookie: prov=20629ccd-8b0f-e8ef-2935-cd26609fc0bc; __qca=P0-1591065732-1479167353442; _ga=GA1.2.1298898376.1479167354; _gat=1; sgt=id=9519gfde_3347_4762_8762_df51458c8ec2; acct=t=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric&s=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric\r\n\r\n";
-
- private static readonly byte[] _plaintextPipelinedRequests = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat(plaintextRequest, Pipelining)));
- private static readonly byte[] _plaintextRequest = Encoding.ASCII.GetBytes(plaintextRequest);
-
- private static readonly byte[] _liveaspnentPipelinedRequests = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat(liveaspnetRequest, Pipelining)));
- private static readonly byte[] _liveaspnentRequest = Encoding.ASCII.GetBytes(liveaspnetRequest);
-
- private static readonly byte[] _unicodePipelinedRequests = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat(unicodeRequest, Pipelining)));
- private static readonly byte[] _unicodeRequest = Encoding.ASCII.GetBytes(unicodeRequest);
-
- [Params(typeof(KestrelHttpParser))]
+ [Params(typeof(Internal.Http.KestrelHttpParser))]
public Type ParserType { get; set; }
- [Benchmark(Baseline = true, OperationsPerInvoke = InnerLoopCount)]
- public void ParsePlaintext()
+ public IPipe Pipe { get; set; }
+
+ public Frame Frame { get; set; }
+
+ public PipeFactory PipelineFactory { get; set; }
+
+ [Setup]
+ public void Setup()
{
- for (var i = 0; i < InnerLoopCount; i++)
+ var connectionContext = new MockConnection(new KestrelServerOptions());
+ connectionContext.ListenerContext.ServiceContext.HttpParserFactory = frame => (IHttpParser)Activator.CreateInstance(ParserType, frame.ConnectionContext.ListenerContext.ServiceContext.Log);
+
+ Frame = new Frame(application: null, context: connectionContext);
+ PipelineFactory = new PipeFactory();
+ Pipe = PipelineFactory.Create();
+ }
+
+ [Benchmark(Baseline = true, OperationsPerInvoke = RequestParsingData.InnerLoopCount)]
+ public void PlaintextTechEmpower()
+ {
+ for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
{
- InsertData(_plaintextRequest);
+ InsertData(RequestParsingData.PlaintextTechEmpowerRequest);
ParseData();
}
}
- [Benchmark(OperationsPerInvoke = InnerLoopCount * Pipelining)]
- public void ParsePipelinedPlaintext()
+ [Benchmark(OperationsPerInvoke = RequestParsingData.InnerLoopCount * RequestParsingData.Pipelining)]
+ public void PipelinedPlaintextTechEmpower()
{
- for (var i = 0; i < InnerLoopCount; i++)
+ for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
{
- InsertData(_plaintextPipelinedRequests);
+ InsertData(RequestParsingData.PlaintextTechEmpowerPipelinedRequests);
ParseData();
}
}
- [Benchmark(OperationsPerInvoke = InnerLoopCount)]
- public void ParseLiveAspNet()
+ [Benchmark(OperationsPerInvoke = RequestParsingData.InnerLoopCount)]
+ public void LiveAspNet()
{
- for (var i = 0; i < InnerLoopCount; i++)
+ for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
{
- InsertData(_liveaspnentRequest);
+ InsertData(RequestParsingData.LiveaspnetRequest);
ParseData();
}
}
- [Benchmark(OperationsPerInvoke = InnerLoopCount * Pipelining)]
- public void ParsePipelinedLiveAspNet()
+ [Benchmark(OperationsPerInvoke = RequestParsingData.InnerLoopCount * RequestParsingData.Pipelining)]
+ public void PipelinedLiveAspNet()
{
- for (var i = 0; i < InnerLoopCount; i++)
+ for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
{
- InsertData(_liveaspnentPipelinedRequests);
+ InsertData(RequestParsingData.LiveaspnetPipelinedRequests);
ParseData();
}
}
- [Benchmark(OperationsPerInvoke = InnerLoopCount)]
- public void ParseUnicode()
+ [Benchmark(OperationsPerInvoke = RequestParsingData.InnerLoopCount)]
+ public void Unicode()
{
- for (var i = 0; i < InnerLoopCount; i++)
+ for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
{
- InsertData(_unicodeRequest);
+ InsertData(RequestParsingData.UnicodeRequest);
ParseData();
}
}
- [Benchmark(OperationsPerInvoke = InnerLoopCount * Pipelining)]
- public void ParseUnicodePipelined()
+ [Benchmark(OperationsPerInvoke = RequestParsingData.InnerLoopCount * RequestParsingData.Pipelining)]
+ public void UnicodePipelined()
{
- for (var i = 0; i < InnerLoopCount; i++)
+ for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
{
- InsertData(_unicodePipelinedRequests);
+ InsertData(RequestParsingData.UnicodePipelinedRequests);
ParseData();
}
}
private void InsertData(byte[] bytes)
{
+ var buffer = Pipe.Writer.Alloc(2048);
+ buffer.WriteFast(bytes);
// There should not be any backpressure and task completes immediately
- Pipe.Writer.WriteAsync(bytes).GetAwaiter().GetResult();
+ buffer.FlushAsync().GetAwaiter().GetResult();
}
private void ParseData()
@@ -143,11 +116,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
Frame.Reset();
- ReadCursor consumed;
- ReadCursor examined;
- if (!Frame.TakeStartLine(readableBuffer, out consumed, out examined))
+ if (!Frame.TakeStartLine(readableBuffer, out var consumed, out var examined))
{
- ThrowInvalidStartLine();
+ ThrowInvalidRequestLine();
}
Pipe.Reader.Advance(consumed, examined);
@@ -156,40 +127,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
Frame.InitializeHeaders();
- if (!Frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)Frame.RequestHeaders, out consumed, out examined))
+ if (!Frame.TakeMessageHeaders(readableBuffer, out consumed, out examined))
{
- ThrowInvalidMessageHeaders();
+ ThrowInvalidRequestHeaders();
}
Pipe.Reader.Advance(consumed, examined);
}
while (true);
}
- private void ThrowInvalidStartLine()
+ public static void ThrowInvalidRequestLine()
{
- throw new InvalidOperationException("Invalid StartLine");
+ throw new InvalidOperationException("Invalid request line");
}
- private void ThrowInvalidMessageHeaders()
+ public static void ThrowInvalidRequestHeaders()
{
- throw new InvalidOperationException("Invalid MessageHeaders");
+ throw new InvalidOperationException("Invalid request headers");
}
-
- [Setup]
- public void Setup()
- {
- var connectionContext = new MockConnection(new KestrelServerOptions());
- connectionContext.ListenerContext.ServiceContext.HttpParserFactory = frame => (IHttpParser)Activator.CreateInstance(ParserType, frame.ConnectionContext.ListenerContext.ServiceContext.Log);
-
- Frame = new Frame(application: null, context: connectionContext);
- PipelineFactory = new PipeFactory();
- Pipe = PipelineFactory.Create();
- }
-
- public IPipe Pipe { get; set; }
-
- public Frame Frame { get; set; }
-
- public PipeFactory PipelineFactory { get; set; }
}
}
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/RequestParsingData.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/RequestParsingData.cs
new file mode 100644
index 0000000000..bddc1a7a97
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/RequestParsingData.cs
@@ -0,0 +1,62 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Linq;
+using System.Text;
+using BenchmarkDotNet.Attributes;
+
+namespace Microsoft.AspNetCore.Server.Kestrel.Performance
+{
+ [Config(typeof(CoreConfig))]
+ public class RequestParsingData
+ {
+ public const int InnerLoopCount = 512;
+
+ public const int Pipelining = 16;
+
+ private const string _plaintextTechEmpowerRequest =
+ "GET /plaintext HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Accept: text/plain,text/html;q=0.9,application/xhtml+xml;q=0.9,application/xml;q=0.8,*/*;q=0.7\r\n" +
+ "Connection: keep-alive\r\n" +
+ "\r\n";
+
+ private const string _liveaspnetRequest =
+ "GET https://live.asp.net/ HTTP/1.1\r\n" +
+ "Host: live.asp.net\r\n" +
+ "Connection: keep-alive\r\n" +
+ "Upgrade-Insecure-Requests: 1\r\n" +
+ "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36\r\n" +
+ "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n" +
+ "DNT: 1\r\n" +
+ "Accept-Encoding: gzip, deflate, sdch, br\r\n" +
+ "Accept-Language: en-US,en;q=0.8\r\n" +
+ "Cookie: __unam=7a67379-1s65dc575c4-6d778abe-1; omniID=9519gfde_3347_4762_8762_df51458c8ec2\r\n" +
+ "\r\n";
+
+ private const string _unicodeRequest =
+ "GET http://stackoverflow.com/questions/40148683/why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric HTTP/1.1\r\n" +
+ "Accept: text/html, application/xhtml+xml, image/jxr, */*\r\n" +
+ "Accept-Language: en-US,en-GB;q=0.7,en;q=0.3\r\n" +
+ "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.14965\r\n" +
+ "Accept-Encoding: gzip, deflate\r\n" +
+ "Host: stackoverflow.com\r\n" +
+ "Connection: Keep-Alive\r\n" +
+ "Cache-Control: max-age=0\r\n" +
+ "Upgrade-Insecure-Requests: 1\r\n" +
+ "DNT: 1\r\n" +
+ "Referer: http://stackoverflow.com/?tab=month\r\n" +
+ "Pragma: no-cache\r\n" +
+ "Cookie: prov=20629ccd-8b0f-e8ef-2935-cd26609fc0bc; __qca=P0-1591065732-1479167353442; _ga=GA1.2.1298898376.1479167354; _gat=1; sgt=id=9519gfde_3347_4762_8762_df51458c8ec2; acct=t=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric&s=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric\r\n" +
+ "\r\n";
+
+ public static readonly byte[] PlaintextTechEmpowerPipelinedRequests = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat(_plaintextTechEmpowerRequest, Pipelining)));
+ public static readonly byte[] PlaintextTechEmpowerRequest = Encoding.ASCII.GetBytes(_plaintextTechEmpowerRequest);
+
+ public static readonly byte[] LiveaspnetPipelinedRequests = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat(_liveaspnetRequest, Pipelining)));
+ public static readonly byte[] LiveaspnetRequest = Encoding.ASCII.GetBytes(_liveaspnetRequest);
+
+ public static readonly byte[] UnicodePipelinedRequests = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat(_unicodeRequest, Pipelining)));
+ public static readonly byte[] UnicodeRequest = Encoding.ASCII.GetBytes(_unicodeRequest);
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/WritableBufferExtensions.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/WritableBufferExtensions.cs
new file mode 100644
index 0000000000..04a538e4be
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/WritableBufferExtensions.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.IO.Pipelines;
+using System.Text;
+
+namespace Microsoft.AspNetCore.Server.Kestrel.Performance
+{
+ internal static class WritableBufferExtensions
+ {
+ public static void WriteFast(this WritableBuffer buffer, ReadOnlySpan source)
+ {
+ if (buffer.Memory.IsEmpty)
+ {
+ buffer.Ensure();
+ }
+
+ // Fast path, try copying to the available memory directly
+ if (source.Length <= buffer.Memory.Length)
+ {
+ source.CopyToFast(buffer.Memory.Span);
+ buffer.Advance(source.Length);
+ return;
+ }
+
+ var remaining = source.Length;
+ var offset = 0;
+
+ while (remaining > 0)
+ {
+ var writable = Math.Min(remaining, buffer.Memory.Length);
+
+ buffer.Ensure(writable);
+
+ if (writable == 0)
+ {
+ continue;
+ }
+
+ source.Slice(offset, writable).CopyToFast(buffer.Memory.Span);
+
+ remaining -= writable;
+ offset += writable;
+
+ buffer.Advance(writable);
+ }
+ }
+
+ private unsafe static void CopyToFast(this ReadOnlySpan source, Span destination)
+ {
+ if (destination.Length < source.Length)
+ {
+ throw new InvalidOperationException();
+ }
+
+ // Assume it fits
+ fixed (byte* pSource = &source.DangerousGetPinnableReference())
+ fixed (byte* pDest = &destination.DangerousGetPinnableReference())
+ {
+ Buffer.MemoryCopy(pSource, pDest, destination.Length, source.Length);
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/configs/CoreConfig.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/configs/CoreConfig.cs
index 1621a1ddc2..01f4c02318 100644
--- a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/configs/CoreConfig.cs
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/configs/CoreConfig.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Validators;
@@ -13,6 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
public CoreConfig()
{
Add(JitOptimizationsValidator.FailOnError);
+ Add(MemoryDiagnoser.Default);
Add(new RpsColumn());
Add(Job.Default
diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/AsciiDecoding.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/AsciiDecoding.cs
index 220ae3d80b..5252a96e9a 100644
--- a/test/Microsoft.AspNetCore.Server.KestrelTests/AsciiDecoding.cs
+++ b/test/Microsoft.AspNetCore.Server.KestrelTests/AsciiDecoding.cs
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
using System.Linq;
using Microsoft.AspNetCore.Server.Kestrel;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
@@ -14,27 +15,16 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
private void FullAsciiRangeSupported()
{
var byteRange = Enumerable.Range(1, 127).Select(x => (byte)x).ToArray();
- using (var pool = new MemoryPool())
+ var s = new Span(byteRange).GetAsciiStringNonNullCharacters();
+
+ Assert.Equal(s.Length, byteRange.Length);
+
+ for (var i = 1; i < byteRange.Length; i++)
{
- var mem = pool.Lease();
- mem.GetIterator().CopyFrom(byteRange);
+ var sb = (byte)s[i];
+ var b = byteRange[i];
- var begin = mem.GetIterator();
- var end = GetIterator(begin, byteRange.Length);
-
- var s = begin.GetAsciiString(ref end);
-
- Assert.Equal(s.Length, byteRange.Length);
-
- for (var i = 1; i < byteRange.Length; i++)
- {
- var sb = (byte)s[i];
- var b = byteRange[i];
-
- Assert.Equal(sb, b);
- }
-
- pool.Return(mem);
+ Assert.Equal(sb, b);
}
}
@@ -49,123 +39,29 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{
var byteRange = Enumerable.Range(1, length).Select(x => (byte)x).ToArray();
byteRange[position] = b;
-
- using (var pool = new MemoryPool())
- {
- var mem = pool.Lease();
- mem.GetIterator().CopyFrom(byteRange);
-
- var begin = mem.GetIterator();
- var end = GetIterator(begin, byteRange.Length);
-
- Assert.Throws(() => begin.GetAsciiString(ref end));
-
- pool.Return(mem);
- }
+
+ Assert.Throws(() => new Span(byteRange).GetAsciiStringNonNullCharacters());
}
}
}
- [Fact]
- private void MultiBlockProducesCorrectResults()
- {
- var byteRange = Enumerable.Range(0, 512 + 64).Select(x => (byte)((x & 0x7f) | 0x01)).ToArray();
- var expectedByteRange = byteRange
- .Concat(byteRange)
- .Concat(byteRange)
- .Concat(byteRange)
- .ToArray();
-
- using (var pool = new MemoryPool())
- {
- var mem0 = pool.Lease();
- var mem1 = pool.Lease();
- var mem2 = pool.Lease();
- var mem3 = pool.Lease();
- mem0.GetIterator().CopyFrom(byteRange);
- mem1.GetIterator().CopyFrom(byteRange);
- mem2.GetIterator().CopyFrom(byteRange);
- mem3.GetIterator().CopyFrom(byteRange);
-
- mem0.Next = mem1;
- mem1.Next = mem2;
- mem2.Next = mem3;
-
- var begin = mem0.GetIterator();
- var end = GetIterator(begin, expectedByteRange.Length);
-
- var s = begin.GetAsciiString(ref end);
-
- Assert.Equal(s.Length, expectedByteRange.Length);
-
- for (var i = 0; i < expectedByteRange.Length; i++)
- {
- var sb = (byte)((s[i] & 0x7f) | 0x01);
- var b = expectedByteRange[i];
-
- Assert.Equal(sb, b);
- }
-
- pool.Return(mem0);
- pool.Return(mem1);
- pool.Return(mem2);
- pool.Return(mem3);
- }
- }
-
[Fact]
private void LargeAllocationProducesCorrectResults()
{
var byteRange = Enumerable.Range(0, 16384 + 64).Select(x => (byte)((x & 0x7f) | 0x01)).ToArray();
var expectedByteRange = byteRange.Concat(byteRange).ToArray();
- using (var pool = new MemoryPool())
+
+ var s = new Span(expectedByteRange).GetAsciiStringNonNullCharacters();
+
+ Assert.Equal(expectedByteRange.Length, s.Length);
+
+ for (var i = 0; i < expectedByteRange.Length; i++)
{
- var mem0 = pool.Lease();
- var mem1 = pool.Lease();
- mem0.GetIterator().CopyFrom(byteRange);
- mem1.GetIterator().CopyFrom(byteRange);
+ var sb = (byte)((s[i] & 0x7f) | 0x01);
+ var b = expectedByteRange[i];
- var lastBlock = mem0;
- while (lastBlock.Next != null)
- {
- lastBlock = lastBlock.Next;
- }
- lastBlock.Next = mem1;
-
- var begin = mem0.GetIterator();
- var end = GetIterator(begin, expectedByteRange.Length);
-
- var s = begin.GetAsciiString(ref end);
-
- Assert.Equal(expectedByteRange.Length, s.Length);
-
- for (var i = 0; i < expectedByteRange.Length; i++)
- {
- var sb = (byte)((s[i] & 0x7f) | 0x01);
- var b = expectedByteRange[i];
-
- Assert.Equal(sb, b);
- }
-
- var block = mem0;
- while (block != null)
- {
- var returnBlock = block;
- block = block.Next;
- pool.Return(returnBlock);
- }
+ Assert.Equal(sb, b);
}
}
-
- private MemoryPoolIterator GetIterator(MemoryPoolIterator begin, int displacement)
- {
- var result = begin;
- for (int i = 0; i < displacement; ++i)
- {
- result.Take();
- }
-
- return result;
- }
}
}
diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/FrameRequestHeadersTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/FrameRequestHeadersTests.cs
index dbf6d857a5..006ac65cd1 100644
--- a/test/Microsoft.AspNetCore.Server.KestrelTests/FrameRequestHeadersTests.cs
+++ b/test/Microsoft.AspNetCore.Server.KestrelTests/FrameRequestHeadersTests.cs
@@ -304,14 +304,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
}
[Fact]
- public void AppendThrowsWhenHeaderValueContainsNonASCIICharacters()
+ public void AppendThrowsWhenHeaderNameContainsNonASCIICharacters()
{
var headers = new FrameRequestHeaders();
const string key = "\u00141d\017c";
var encoding = Encoding.GetEncoding("iso-8859-1");
var exception = Assert.Throws(
- () => headers.Append(encoding.GetBytes(key), key));
+ () => headers.Append(encoding.GetBytes(key), "value"));
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
}
}
diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/FrameTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/FrameTests.cs
index f4b4339479..8aeb066862 100644
--- a/test/Microsoft.AspNetCore.Server.KestrelTests/FrameTests.cs
+++ b/test/Microsoft.AspNetCore.Server.KestrelTests/FrameTests.cs
@@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{
public class FrameTests : IDisposable
{
- private readonly IPipe _socketInput;
+ private readonly IPipe _input;
private readonly TestFrame _frame;
private readonly ServiceContext _serviceContext;
private readonly ConnectionContext _connectionContext;
@@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{
var trace = new KestrelTrace(new TestKestrelTrace());
_pipelineFactory = new PipeFactory();
- _socketInput = _pipelineFactory.Create();
+ _input = _pipelineFactory.Create();
_serviceContext = new ServiceContext
{
@@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
};
_connectionContext = new ConnectionContext(listenerContext)
{
- Input = _socketInput,
+ Input = _input,
Output = new MockSocketOutput(),
ConnectionControl = Mock.Of()
};
@@ -76,130 +76,27 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
public void Dispose()
{
- _socketInput.Reader.Complete();
- _socketInput.Writer.Complete();
+ _input.Reader.Complete();
+ _input.Writer.Complete();
_pipelineFactory.Dispose();
}
- [Fact]
- public async Task CanReadHeaderValueWithoutLeadingWhitespace()
- {
- _frame.InitializeHeaders();
-
- await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("Header:value\r\n\r\n"));
-
- var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
- var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
- _socketInput.Reader.Advance(_consumed, _examined);
-
- Assert.True(success);
- Assert.Equal(1, _frame.RequestHeaders.Count);
- Assert.Equal("value", _frame.RequestHeaders["Header"]);
- Assert.Equal(readableBuffer.End, _consumed);
- }
-
- [Theory]
- [InlineData("Header: value\r\n\r\n")]
- [InlineData("Header: value\r\n\r\n")]
- [InlineData("Header:\tvalue\r\n\r\n")]
- [InlineData("Header: \tvalue\r\n\r\n")]
- [InlineData("Header:\t value\r\n\r\n")]
- [InlineData("Header:\t\tvalue\r\n\r\n")]
- [InlineData("Header:\t\t value\r\n\r\n")]
- [InlineData("Header: \t\tvalue\r\n\r\n")]
- [InlineData("Header: \t\t value\r\n\r\n")]
- [InlineData("Header: \t \t value\r\n\r\n")]
- public async Task LeadingWhitespaceIsNotIncludedInHeaderValue(string rawHeaders)
- {
- await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
-
- var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
- var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
- _socketInput.Reader.Advance(_consumed, _examined);
-
- Assert.True(success);
- Assert.Equal(1, _frame.RequestHeaders.Count);
- Assert.Equal("value", _frame.RequestHeaders["Header"]);
- Assert.Equal(readableBuffer.End, _consumed);
- }
-
- [Theory]
- [InlineData("Header: value \r\n\r\n")]
- [InlineData("Header: value\t\r\n\r\n")]
- [InlineData("Header: value \t\r\n\r\n")]
- [InlineData("Header: value\t \r\n\r\n")]
- [InlineData("Header: value\t\t\r\n\r\n")]
- [InlineData("Header: value\t\t \r\n\r\n")]
- [InlineData("Header: value \t\t\r\n\r\n")]
- [InlineData("Header: value \t\t \r\n\r\n")]
- [InlineData("Header: value \t \t \r\n\r\n")]
- public async Task TrailingWhitespaceIsNotIncludedInHeaderValue(string rawHeaders)
- {
- await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
-
- var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
- var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
- _socketInput.Reader.Advance(_consumed, _examined);
-
- Assert.True(success);
- Assert.Equal(1, _frame.RequestHeaders.Count);
- Assert.Equal("value", _frame.RequestHeaders["Header"]);
- Assert.Equal(readableBuffer.End, _consumed);
- }
-
- [Theory]
- [InlineData("Header: one two three\r\n\r\n", "one two three")]
- [InlineData("Header: one two three\r\n\r\n", "one two three")]
- [InlineData("Header: one\ttwo\tthree\r\n\r\n", "one\ttwo\tthree")]
- [InlineData("Header: one two\tthree\r\n\r\n", "one two\tthree")]
- [InlineData("Header: one\ttwo three\r\n\r\n", "one\ttwo three")]
- [InlineData("Header: one \ttwo \tthree\r\n\r\n", "one \ttwo \tthree")]
- [InlineData("Header: one\t two\t three\r\n\r\n", "one\t two\t three")]
- [InlineData("Header: one \ttwo\t three\r\n\r\n", "one \ttwo\t three")]
- public async Task WhitespaceWithinHeaderValueIsPreserved(string rawHeaders, string expectedValue)
- {
- await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
-
- var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
- var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
- _socketInput.Reader.Advance(_consumed, _examined);
-
- Assert.True(success);
- Assert.Equal(1, _frame.RequestHeaders.Count);
- Assert.Equal(expectedValue, _frame.RequestHeaders["Header"]);
- Assert.Equal(readableBuffer.End, _consumed);
- }
-
- [Theory]
- [MemberData(nameof(InvalidRequestHeaderData))]
- public async Task TakeMessageHeadersThrowsOnInvalidRequestHeaders(string rawHeaders, string expectedExceptionMessage)
- {
- await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
- var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
-
- var exception = Assert.Throws(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined));
- _socketInput.Reader.Advance(_consumed, _examined);
-
- Assert.Equal(expectedExceptionMessage, exception.Message);
- Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
- }
-
[Fact]
public async Task TakeMessageHeadersThrowsOnHeaderValueWithLineFolding_CharacterNotAvailableOnFirstAttempt()
{
- await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("Header-1: value1\r\n"));
+ await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes("Header-1: value1\r\n"));
- var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
- Assert.False(_frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined));
- _socketInput.Reader.Advance(_consumed, _examined);
+ var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
+ Assert.False(_frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
+ _input.Reader.Advance(_consumed, _examined);
- await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(" "));
+ await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes(" "));
- readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
- var exception = Assert.Throws(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined));
- _socketInput.Reader.Advance(_consumed, _examined);
+ readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
+ var exception = Assert.Throws(() => _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
+ _input.Reader.Advance(_consumed, _examined);
- Assert.Equal("Header value line folding not supported.", exception.Message);
+ Assert.Equal("Whitespace is not allowed in header name.", exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
}
@@ -210,11 +107,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
_serviceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize = headerLine.Length - 1;
_frame.Reset();
- await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine}\r\n"));
- var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
+ await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine}\r\n"));
+ var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
- var exception = Assert.Throws(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined));
- _socketInput.Reader.Advance(_consumed, _examined);
+ var exception = Assert.Throws(() => _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
+ _input.Reader.Advance(_consumed, _examined);
Assert.Equal("Request headers too long.", exception.Message);
Assert.Equal(StatusCodes.Status431RequestHeaderFieldsTooLarge, exception.StatusCode);
@@ -226,36 +123,16 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
const string headerLines = "Header-1: value1\r\nHeader-2: value2\r\n";
_serviceContext.ServerOptions.Limits.MaxRequestHeaderCount = 1;
- await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLines}\r\n"));
- var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
+ await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLines}\r\n"));
+ var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
- var exception = Assert.Throws(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined));
- _socketInput.Reader.Advance(_consumed, _examined);
+ var exception = Assert.Throws(() => _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
+ _input.Reader.Advance(_consumed, _examined);
Assert.Equal("Request contains too many headers.", exception.Message);
Assert.Equal(StatusCodes.Status431RequestHeaderFieldsTooLarge, exception.StatusCode);
}
- [Theory]
- [InlineData("Cookie: \r\n\r\n", 1)]
- [InlineData("Cookie:\r\n\r\n", 1)]
- [InlineData("Cookie: \r\nConnection: close\r\n\r\n", 2)]
- [InlineData("Cookie:\r\nConnection: close\r\n\r\n", 2)]
- [InlineData("Connection: close\r\nCookie: \r\n\r\n", 2)]
- [InlineData("Connection: close\r\nCookie:\r\n\r\n", 2)]
- public async Task EmptyHeaderValuesCanBeParsed(string rawHeaders, int numHeaders)
- {
- await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
- var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
-
- var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
- _socketInput.Reader.Advance(_consumed, _examined);
-
- Assert.True(success);
- Assert.Equal(numHeaders, _frame.RequestHeaders.Count);
- Assert.Equal(readableBuffer.End, _consumed);
- }
-
[Fact]
public void ResetResetsScheme()
{
@@ -279,11 +156,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
options.Limits.MaxRequestHeaderCount = 1;
_serviceContext.ServerOptions = options;
- await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine1}\r\n"));
- var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
+ await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine1}\r\n"));
+ var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
- var takeMessageHeaders = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
- _socketInput.Reader.Advance(_consumed, _examined);
+ var takeMessageHeaders = _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined);
+ _input.Reader.Advance(_consumed, _examined);
Assert.True(takeMessageHeaders);
Assert.Equal(1, _frame.RequestHeaders.Count);
@@ -291,11 +168,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
_frame.Reset();
- await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine2}\r\n"));
- readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
+ await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine2}\r\n"));
+ readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
- takeMessageHeaders = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
- _socketInput.Reader.Advance(_consumed, _examined);
+ takeMessageHeaders = _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined);
+ _input.Reader.Advance(_consumed, _examined);
Assert.True(takeMessageHeaders);
Assert.Equal(1, _frame.RequestHeaders.Count);
@@ -390,86 +267,37 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
public async Task TakeStartLineSetsFrameProperties(
string requestLine,
string expectedMethod,
- string expectedPath,
+ string expectedRawTarget,
+ string expectedRawPath,
+ string expectedDecodedPath,
string expectedQueryString,
string expectedHttpVersion)
{
var requestLineBytes = Encoding.ASCII.GetBytes(requestLine);
- await _socketInput.Writer.WriteAsync(requestLineBytes);
- var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
+ await _input.Writer.WriteAsync(requestLineBytes);
+ var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
var returnValue = _frame.TakeStartLine(readableBuffer, out _consumed, out _examined);
- _socketInput.Reader.Advance(_consumed, _examined);
+ _input.Reader.Advance(_consumed, _examined);
Assert.True(returnValue);
Assert.Equal(expectedMethod, _frame.Method);
- Assert.Equal(expectedPath, _frame.Path);
+ Assert.Equal(expectedRawTarget, _frame.RawTarget);
+ Assert.Equal(expectedDecodedPath, _frame.Path);
Assert.Equal(expectedQueryString, _frame.QueryString);
Assert.Equal(expectedHttpVersion, _frame.HttpVersion);
}
[Fact]
- public async Task TakeStartLineCallsConsumingCompleteWithFurthestExamined()
- {
- var requestLineBytes = Encoding.ASCII.GetBytes("GET / ");
- await _socketInput.Writer.WriteAsync(requestLineBytes);
- var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
-
- _frame.TakeStartLine(readableBuffer, out _consumed, out _examined);
- _socketInput.Reader.Advance(_consumed, _examined);
-
- Assert.Equal(readableBuffer.Start, _consumed);
- Assert.Equal(readableBuffer.End, _examined);
-
- requestLineBytes = Encoding.ASCII.GetBytes("HTTP/1.1\r\n");
- await _socketInput.Writer.WriteAsync(requestLineBytes);
- readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
-
- _frame.TakeStartLine(readableBuffer, out _consumed, out _examined);
- _socketInput.Reader.Advance(_consumed, _examined);
-
- Assert.Equal(readableBuffer.End, _consumed);
- Assert.Equal(readableBuffer.End, _examined);
- }
-
- [Theory]
- [InlineData("G")]
- [InlineData("GE")]
- [InlineData("GET")]
- [InlineData("GET ")]
- [InlineData("GET /")]
- [InlineData("GET / ")]
- [InlineData("GET / H")]
- [InlineData("GET / HT")]
- [InlineData("GET / HTT")]
- [InlineData("GET / HTTP")]
- [InlineData("GET / HTTP/")]
- [InlineData("GET / HTTP/1")]
- [InlineData("GET / HTTP/1.")]
- [InlineData("GET / HTTP/1.1")]
- [InlineData("GET / HTTP/1.1\r")]
- public async Task TakeStartLineReturnsWhenGivenIncompleteRequestLines(string requestLine)
- {
- var requestLineBytes = Encoding.ASCII.GetBytes(requestLine);
- await _socketInput.Writer.WriteAsync(requestLineBytes);
-
- var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
- var returnValue = _frame.TakeStartLine(readableBuffer, out _consumed, out _examined);
- _socketInput.Reader.Advance(_consumed, _examined);
-
- Assert.False(returnValue);
- }
-
- [Fact]
- public async Task TakeStartLineStartsRequestHeadersTimeoutOnFirstByteAvailable()
+ public async Task ParseRequestStartsRequestHeadersTimeoutOnFirstByteAvailable()
{
var connectionControl = new Mock();
_connectionContext.ConnectionControl = connectionControl.Object;
- await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("G"));
+ await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes("G"));
- _frame.TakeStartLine((await _socketInput.Reader.ReadAsync()).Buffer, out _consumed, out _examined);
- _socketInput.Reader.Advance(_consumed, _examined);
+ _frame.ParseRequest((await _input.Reader.ReadAsync()).Buffer, out _consumed, out _examined);
+ _input.Reader.Advance(_consumed, _examined);
var expectedRequestHeadersTimeout = (long)_serviceContext.ServerOptions.Limits.RequestHeadersTimeout.TotalMilliseconds;
connectionControl.Verify(cc => cc.ResetTimeout(expectedRequestHeadersTimeout, TimeoutAction.SendTimeoutResponse));
@@ -481,93 +309,42 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
_serviceContext.ServerOptions.Limits.MaxRequestLineSize = "GET / HTTP/1.1\r\n".Length;
var requestLineBytes = Encoding.ASCII.GetBytes("GET /a HTTP/1.1\r\n");
- await _socketInput.Writer.WriteAsync(requestLineBytes);
+ await _input.Writer.WriteAsync(requestLineBytes);
- var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
+ var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws(() => _frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
- _socketInput.Reader.Advance(_consumed, _examined);
+ _input.Reader.Advance(_consumed, _examined);
Assert.Equal("Request line too long.", exception.Message);
Assert.Equal(StatusCodes.Status414UriTooLong, exception.StatusCode);
}
[Theory]
- [MemberData(nameof(InvalidRequestLineData))]
- public async Task TakeStartLineThrowsOnInvalidRequestLine(string requestLine, Type expectedExceptionType, string expectedExceptionMessage)
+ [MemberData(nameof(RequestLineWithEncodedNullCharInTargetData))]
+ public async Task TakeStartLineThrowsOnEncodedNullCharInTarget(string requestLine)
{
- await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
- var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
+ await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
+ var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
- var exception = Assert.Throws(expectedExceptionType, () =>
+ var exception = Assert.Throws(() =>
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
- _socketInput.Reader.Advance(_consumed, _examined);
+ _input.Reader.Advance(_consumed, _examined);
- Assert.Equal(expectedExceptionMessage, exception.Message);
-
- if (expectedExceptionType == typeof(BadHttpRequestException))
- {
- Assert.Equal(StatusCodes.Status400BadRequest, (exception as BadHttpRequestException).StatusCode);
- }
+ Assert.Equal("The path contains null characters.", exception.Message);
}
[Theory]
- [MemberData(nameof(UnrecognizedHttpVersionData))]
- public async Task TakeStartLineThrowsOnUnrecognizedHttpVersion(string httpVersion)
+ [MemberData(nameof(RequestLineWithNullCharInTargetData))]
+ public async Task TakeStartLineThrowsOnNullCharInTarget(string requestLine)
{
- await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"GET / {httpVersion}\r\n"));
+ await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
+ var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
- var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
+ var exception = Assert.Throws(() =>
+ _frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
+ _input.Reader.Advance(_consumed, _examined);
- var exception = Assert.Throws(() => _frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
- _socketInput.Reader.Advance(_consumed, _examined);
-
- Assert.Equal($"Unrecognized HTTP version: {httpVersion}", exception.Message);
- Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode);
- }
-
- [Fact]
- public async Task TakeMessageHeadersCallsConsumingCompleteWithFurthestExamined()
- {
- foreach (var rawHeader in new[] { "Header: ", "value\r\n", "\r\n" })
- {
- await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeader));
-
- var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
- _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
- _socketInput.Reader.Advance(_consumed, _examined);
- Assert.Equal(readableBuffer.End, _examined);
- }
- }
-
- [Theory]
- [InlineData("\r")]
- [InlineData("H")]
- [InlineData("He")]
- [InlineData("Hea")]
- [InlineData("Head")]
- [InlineData("Heade")]
- [InlineData("Header")]
- [InlineData("Header:")]
- [InlineData("Header: ")]
- [InlineData("Header: v")]
- [InlineData("Header: va")]
- [InlineData("Header: val")]
- [InlineData("Header: valu")]
- [InlineData("Header: value")]
- [InlineData("Header: value\r")]
- [InlineData("Header: value\r\n")]
- [InlineData("Header: value\r\n\r")]
- public async Task TakeMessageHeadersReturnsWhenGivenIncompleteHeaders(string headers)
- {
- var headerBytes = Encoding.ASCII.GetBytes(headers);
- await _socketInput.Writer.WriteAsync(headerBytes);
-
- ReadCursor consumed;
- ReadCursor examined;
- var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
-
- Assert.Equal(false, _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
- _socketInput.Reader.Advance(consumed, examined);
+ Assert.Equal(new InvalidOperationException().Message, exception.Message);
}
[Fact]
@@ -582,7 +359,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
connectionControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.CloseConnection));
_frame.StopAsync();
- _socketInput.Writer.Complete();
+ _input.Writer.Complete();
requestProcessingTask.Wait();
}
@@ -664,13 +441,13 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
_frame.Start();
var data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n\r\n");
- await _socketInput.Writer.WriteAsync(data);
+ await _input.Writer.WriteAsync(data);
var requestProcessingTask = _frame.StopAsync();
Assert.IsNotType(typeof(Task), requestProcessingTask);
await requestProcessingTask.TimeoutAfter(TimeSpan.FromSeconds(10));
- _socketInput.Writer.Complete();
+ _input.Writer.Complete();
}
[Fact]
@@ -741,12 +518,36 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
Assert.NotSame(original, _frame.RequestAborted.WaitHandle);
}
- public static IEnumerable ValidRequestLineData => HttpParsingData.ValidRequestLineData;
+ public static IEnumerable ValidRequestLineData => HttpParsingData.RequestLineValidData;
- public static IEnumerable InvalidRequestLineData => HttpParsingData.InvalidRequestLineData;
+ public static TheoryData RequestLineWithEncodedNullCharInTargetData
+ {
+ get
+ {
+ var data = new TheoryData();
- public static TheoryData UnrecognizedHttpVersionData => HttpParsingData.UnrecognizedHttpVersionData;
+ foreach (var requestLine in HttpParsingData.RequestLineWithEncodedNullCharInTargetData)
+ {
+ data.Add(requestLine);
+ }
- public static IEnumerable InvalidRequestHeaderData => HttpParsingData.InvalidRequestHeaderData;
+ return data;
+ }
+ }
+
+ public static TheoryData RequestLineWithNullCharInTargetData
+ {
+ get
+ {
+ var data = new TheoryData();
+
+ foreach (var requestLine in HttpParsingData.RequestLineWithNullCharInTargetData)
+ {
+ data.Add(requestLine);
+ }
+
+ return data;
+ }
+ }
}
}
diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/HttpParserTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/HttpParserTests.cs
new file mode 100644
index 0000000000..7d16b5f06c
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Server.KestrelTests/HttpParserTests.cs
@@ -0,0 +1,383 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO.Pipelines;
+using System.Linq;
+using System.Text;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Server.Kestrel;
+using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
+using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
+using Microsoft.AspNetCore.Testing;
+using Microsoft.Extensions.Logging;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Server.KestrelTests
+{
+ public class HttpParserTests
+ {
+ // Returns true when all headers parsed
+ // Return false otherwise
+
+ [Theory]
+ [MemberData(nameof(RequestLineValidData))]
+ public void ParsesRequestLine(
+ string requestLine,
+ string expectedMethod,
+ string expectedRawTarget,
+ string expectedRawPath,
+ string expectedDecodedPath,
+ string expectedQueryString,
+ string expectedVersion)
+ {
+ var parser = CreateParser(Mock.Of());
+ var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(requestLine));
+
+ string parsedMethod = null;
+ string parsedVersion = null;
+ string parsedRawTarget = null;
+ string parsedRawPath = null;
+ string parsedQuery = null;
+ var requestLineHandler = new Mock();
+ requestLineHandler
+ .Setup(handler => handler.OnStartLine(
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny>(),
+ It.IsAny>(),
+ It.IsAny>(),
+ It.IsAny>()))
+ .Callback, Span, Span, Span>((method, version, target, path, query, customMethod) =>
+ {
+ parsedMethod = method != HttpMethod.Custom ? HttpUtilities.MethodToString(method) : customMethod.GetAsciiStringNonNullCharacters();
+ parsedVersion = HttpUtilities.VersionToString(version);
+ parsedRawTarget = target.GetAsciiStringNonNullCharacters();
+ parsedRawPath = path.GetAsciiStringNonNullCharacters();
+ parsedQuery = query.GetAsciiStringNonNullCharacters();
+ });
+
+ Assert.True(parser.ParseRequestLine(requestLineHandler.Object, buffer, out var consumed, out var examined));
+
+ Assert.Equal(parsedMethod, expectedMethod);
+ Assert.Equal(parsedVersion, expectedVersion);
+ Assert.Equal(parsedRawTarget, expectedRawTarget);
+ Assert.Equal(parsedRawPath, expectedRawPath);
+ Assert.Equal(parsedVersion, expectedVersion);
+ Assert.Equal(buffer.End, consumed);
+ Assert.Equal(buffer.End, examined);
+ }
+
+ [Theory]
+ [MemberData(nameof(RequestLineIncompleteData))]
+ public void ParseRequestLineReturnsFalseWhenGivenIncompleteRequestLines(string requestLine)
+ {
+ var parser = CreateParser(Mock.Of());
+ var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(requestLine));
+
+ Assert.False(parser.ParseRequestLine(Mock.Of(), buffer, out var consumed, out var examined));
+ }
+
+ [Theory]
+ [MemberData(nameof(RequestLineIncompleteData))]
+ public void ParseRequestLineDoesNotConsumeIncompleteRequestLine(string requestLine)
+ {
+ var parser = CreateParser(Mock.Of());
+ var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(requestLine));
+
+ Assert.False(parser.ParseRequestLine(Mock.Of(), buffer, out var consumed, out var examined));
+
+ Assert.Equal(buffer.Start, consumed);
+ Assert.Equal(buffer.End, examined);
+ }
+
+ [Theory]
+ [MemberData(nameof(RequestLineInvalidData))]
+ public void ParseRequestLineThrowsOnInvalidRequestLine(string requestLine)
+ {
+ var mockTrace = new Mock();
+ mockTrace
+ .Setup(trace => trace.IsEnabled(LogLevel.Information))
+ .Returns(true);
+
+ var parser = CreateParser(mockTrace.Object);
+ var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(requestLine));
+
+ var exception = Assert.Throws(() =>
+ parser.ParseRequestLine(Mock.Of(), buffer, out var consumed, out var examined));
+
+ Assert.Equal($"Invalid request line: {requestLine.Replace("\r", "<0x0D>").Replace("\n", "<0x0A>")}", exception.Message);
+ Assert.Equal(StatusCodes.Status400BadRequest, (exception as BadHttpRequestException).StatusCode);
+ }
+
+ [Theory]
+ [MemberData(nameof(UnrecognizedHttpVersionData))]
+ public void ParseRequestLineThrowsOnUnrecognizedHttpVersion(string httpVersion)
+ {
+ var requestLine = $"GET / {httpVersion}\r\n";
+
+ var mockTrace = new Mock();
+ mockTrace
+ .Setup(trace => trace.IsEnabled(LogLevel.Information))
+ .Returns(true);
+
+ var parser = CreateParser(mockTrace.Object);
+ var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(requestLine));
+
+ var exception = Assert.Throws(() =>
+ parser.ParseRequestLine(Mock.Of(), buffer, out var consumed, out var examined));
+
+ Assert.Equal($"Unrecognized HTTP version: {httpVersion}", exception.Message);
+ Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, (exception as BadHttpRequestException).StatusCode);
+ }
+
+ [Theory]
+ [InlineData("\r")]
+ [InlineData("H")]
+ [InlineData("He")]
+ [InlineData("Hea")]
+ [InlineData("Head")]
+ [InlineData("Heade")]
+ [InlineData("Header")]
+ [InlineData("Header:")]
+ [InlineData("Header: ")]
+ [InlineData("Header: v")]
+ [InlineData("Header: va")]
+ [InlineData("Header: val")]
+ [InlineData("Header: valu")]
+ [InlineData("Header: value")]
+ [InlineData("Header: value\r")]
+ [InlineData("Header: value\r\n")]
+ [InlineData("Header: value\r\n\r")]
+ [InlineData("Header-1: value1\r\nH")]
+ [InlineData("Header-1: value1\r\nHe")]
+ [InlineData("Header-1: value1\r\nHea")]
+ [InlineData("Header-1: value1\r\nHead")]
+ [InlineData("Header-1: value1\r\nHeade")]
+ [InlineData("Header-1: value1\r\nHeader")]
+ [InlineData("Header-1: value1\r\nHeader-")]
+ [InlineData("Header-1: value1\r\nHeader-2")]
+ [InlineData("Header-1: value1\r\nHeader-2:")]
+ [InlineData("Header-1: value1\r\nHeader-2: ")]
+ [InlineData("Header-1: value1\r\nHeader-2: v")]
+ [InlineData("Header-1: value1\r\nHeader-2: va")]
+ [InlineData("Header-1: value1\r\nHeader-2: val")]
+ [InlineData("Header-1: value1\r\nHeader-2: valu")]
+ [InlineData("Header-1: value1\r\nHeader-2: value")]
+ [InlineData("Header-1: value1\r\nHeader-2: value2")]
+ [InlineData("Header-1: value1\r\nHeader-2: value2\r")]
+ [InlineData("Header-1: value1\r\nHeader-2: value2\r\n")]
+ [InlineData("Header-1: value1\r\nHeader-2: value2\r\n\r")]
+ public void ParseHeadersReturnsFalseWhenGivenIncompleteHeaders(string rawHeaders)
+ {
+ var parser = CreateParser(Mock.Of());
+
+ var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(rawHeaders));
+ Assert.False(parser.ParseHeaders(Mock.Of(), buffer, out var consumed, out var examined, out var consumedBytes));
+ }
+
+ [Theory]
+ [InlineData("\r")]
+ [InlineData("H")]
+ [InlineData("He")]
+ [InlineData("Hea")]
+ [InlineData("Head")]
+ [InlineData("Heade")]
+ [InlineData("Header")]
+ [InlineData("Header:")]
+ [InlineData("Header: ")]
+ [InlineData("Header: v")]
+ [InlineData("Header: va")]
+ [InlineData("Header: val")]
+ [InlineData("Header: valu")]
+ [InlineData("Header: value")]
+ [InlineData("Header: value\r")]
+ public void ParseHeadersDoesNotConsumeIncompleteHeader(string rawHeaders)
+ {
+ var parser = CreateParser(Mock.Of());
+
+ var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(rawHeaders));
+ parser.ParseHeaders(Mock.Of(), buffer, out var consumed, out var examined, out var consumedBytes);
+
+ Assert.Equal(buffer.Start, consumed);
+ Assert.Equal(buffer.End, examined);
+ Assert.Equal(0, consumedBytes);
+ }
+
+ [Fact]
+ public void ParseHeadersCanReadHeaderValueWithoutLeadingWhitespace()
+ {
+ VerifyHeader("Header", "value", "value");
+ }
+
+ [Theory]
+ [InlineData("Cookie: \r\n\r\n", "Cookie", "", null, null)]
+ [InlineData("Cookie:\r\n\r\n", "Cookie", "", null, null)]
+ [InlineData("Cookie: \r\nConnection: close\r\n\r\n", "Cookie", "", "Connection", "close")]
+ [InlineData("Cookie:\r\nConnection: close\r\n\r\n", "Cookie", "", "Connection", "close")]
+ [InlineData("Connection: close\r\nCookie: \r\n\r\n", "Connection", "close", "Cookie", "")]
+ [InlineData("Connection: close\r\nCookie:\r\n\r\n", "Connection", "close", "Cookie", "")]
+ public void ParseHeadersCanParseEmptyHeaderValues(
+ string rawHeaders,
+ string expectedHeaderName1,
+ string expectedHeaderValue1,
+ string expectedHeaderName2,
+ string expectedHeaderValue2)
+ {
+ var expectedHeaderNames = expectedHeaderName2 == null
+ ? new[] { expectedHeaderName1 }
+ : new[] { expectedHeaderName1, expectedHeaderName2 };
+ var expectedHeaderValues = expectedHeaderValue2 == null
+ ? new[] { expectedHeaderValue1 }
+ : new[] { expectedHeaderValue1, expectedHeaderValue2 };
+
+ VerifyRawHeaders(rawHeaders, expectedHeaderNames, expectedHeaderValues);
+ }
+
+ [Theory]
+ [InlineData(" value")]
+ [InlineData(" value")]
+ [InlineData("\tvalue")]
+ [InlineData(" \tvalue")]
+ [InlineData("\t value")]
+ [InlineData("\t\tvalue")]
+ [InlineData("\t\t value")]
+ [InlineData(" \t\tvalue")]
+ [InlineData(" \t\t value")]
+ [InlineData(" \t \t value")]
+ public void ParseHeadersDoesNotIncludeLeadingWhitespaceInHeaderValue(string rawHeaderValue)
+ {
+ VerifyHeader("Header", rawHeaderValue, "value");
+ }
+
+ [Theory]
+ [InlineData("value ")]
+ [InlineData("value\t")]
+ [InlineData("value \t")]
+ [InlineData("value\t ")]
+ [InlineData("value\t\t")]
+ [InlineData("value\t\t ")]
+ [InlineData("value \t\t")]
+ [InlineData("value \t\t ")]
+ [InlineData("value \t \t ")]
+ public void ParseHeadersDoesNotIncludeTrailingWhitespaceInHeaderValue(string rawHeaderValue)
+ {
+ VerifyHeader("Header", rawHeaderValue, "value");
+ }
+
+ [Theory]
+ [InlineData("one two three")]
+ [InlineData("one two three")]
+ [InlineData("one\ttwo\tthree")]
+ [InlineData("one two\tthree")]
+ [InlineData("one\ttwo three")]
+ [InlineData("one \ttwo \tthree")]
+ [InlineData("one\t two\t three")]
+ [InlineData("one \ttwo\t three")]
+ public void ParseHeadersPreservesWhitespaceWithinHeaderValue(string headerValue)
+ {
+ VerifyHeader("Header", headerValue, headerValue);
+ }
+
+ [Fact]
+ public void ParseHeadersConsumesBytesCorrectlyAtEnd()
+ {
+ var parser = CreateParser(Mock.Of());
+
+ const string headerLine = "Header: value\r\n\r";
+ var buffer1 = ReadableBuffer.Create(Encoding.ASCII.GetBytes(headerLine));
+ Assert.False(parser.ParseHeaders(Mock.Of(), buffer1, out var consumed, out var examined, out var consumedBytes));
+
+ Assert.Equal(buffer1.Move(buffer1.Start, headerLine.Length - 1), consumed);
+ Assert.Equal(buffer1.End, examined);
+ Assert.Equal(headerLine.Length - 1, consumedBytes);
+
+ var buffer2 = ReadableBuffer.Create(Encoding.ASCII.GetBytes("\r\n"));
+ Assert.True(parser.ParseHeaders(Mock.Of(), buffer2, out consumed, out examined, out consumedBytes));
+
+ Assert.Equal(buffer2.End, consumed);
+ Assert.Equal(buffer2.End, examined);
+ Assert.Equal(2, consumedBytes);
+ }
+
+ [Theory]
+ [MemberData(nameof(RequestHeaderInvalidData))]
+ public void ParseHeadersThrowsOnInvalidRequestHeaders(string rawHeaders, string expectedExceptionMessage)
+ {
+ var parser = CreateParser(Mock.Of());
+ var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(rawHeaders));
+
+ var exception = Assert.Throws(() =>
+ parser.ParseHeaders(Mock.Of(), buffer, out var consumed, out var examined, out var consumedBytes));
+
+ Assert.Equal(expectedExceptionMessage, exception.Message);
+ Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
+ }
+
+ private void VerifyHeader(
+ string headerName,
+ string rawHeaderValue,
+ string expectedHeaderValue)
+ {
+ var parser = CreateParser(Mock.Of());
+ var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes($"{headerName}:{rawHeaderValue}\r\n"));
+
+ string parsedHeaderName = "unexpected";
+ string parsedHeaderValue = "unexpected";
+ var headersHandler = new Mock();
+ headersHandler
+ .Setup(handler => handler.OnHeader(It.IsAny>(), It.IsAny>()))
+ .Callback, Span>((name, value) =>
+ {
+ parsedHeaderName = name.GetAsciiStringNonNullCharacters();
+ parsedHeaderValue = value.GetAsciiStringNonNullCharacters();
+ });
+
+ parser.ParseHeaders(headersHandler.Object, buffer, out var consumed, out var examined, out var consumedBytes);
+
+ Assert.Equal(headerName, parsedHeaderName);
+ Assert.Equal(expectedHeaderValue, parsedHeaderValue);
+ Assert.Equal(buffer.End, consumed);
+ Assert.Equal(buffer.End, examined);
+ }
+
+ private void VerifyRawHeaders(string rawHeaders, IEnumerable expectedHeaderNames, IEnumerable expectedHeaderValues)
+ {
+ Assert.True(expectedHeaderNames.Count() == expectedHeaderValues.Count(), $"{nameof(expectedHeaderNames)} and {nameof(expectedHeaderValues)} sizes must match");
+
+ var parser = CreateParser(Mock.Of());
+ var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(rawHeaders));
+
+ var parsedHeaders = new List>();
+ var headersHandler = new Mock();
+ headersHandler
+ .Setup(handler => handler.OnHeader(It.IsAny>(), It.IsAny>()))
+ .Callback, Span>((name, value) =>
+ {
+ parsedHeaders.Add(Tuple.Create(name.GetAsciiStringNonNullCharacters(), value.GetAsciiStringNonNullCharacters()));
+ });
+
+ parser.ParseHeaders(headersHandler.Object, buffer, out var consumed, out var examined, out var consumedBytes);
+
+ Assert.Equal(expectedHeaderNames.Count(), parsedHeaders.Count);
+ Assert.Equal(expectedHeaderNames, parsedHeaders.Select(t => t.Item1));
+ Assert.Equal(expectedHeaderValues, parsedHeaders.Select(t => t.Item2));
+ Assert.Equal(buffer.End, consumed);
+ Assert.Equal(buffer.End, examined);
+ }
+
+ private IHttpParser CreateParser(IKestrelTrace log) => new KestrelHttpParser(log);
+
+ public static IEnumerable RequestLineValidData => HttpParsingData.RequestLineValidData;
+
+ public static IEnumerable RequestLineIncompleteData => HttpParsingData.RequestLineIncompleteData.Select(requestLine => new[] { requestLine });
+
+ public static IEnumerable RequestLineInvalidData => HttpParsingData.RequestLineInvalidData.Select(requestLine => new[] { requestLine });
+
+ public static TheoryData UnrecognizedHttpVersionData => HttpParsingData.UnrecognizedHttpVersionData;
+
+ public static IEnumerable RequestHeaderInvalidData => HttpParsingData.RequestHeaderInvalidData;
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolBlockTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolBlockTests.cs
index 4cde6c079a..2213f81f07 100644
--- a/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolBlockTests.cs
+++ b/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolBlockTests.cs
@@ -8,105 +8,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{
public class MemoryPoolBlockTests
{
- [Fact]
- public void SeekWorks()
- {
- using (var pool = new MemoryPool())
- {
- var block = pool.Lease();
- foreach (var ch in Enumerable.Range(0, 256).Select(x => (byte)x))
- {
- block.Array[block.End++] = ch;
- }
-
- var iterator = block.GetIterator();
- foreach (var ch in Enumerable.Range(0, 256).Select(x => (byte)x))
- {
- var hit = iterator;
- hit.Seek(ch);
- Assert.Equal(ch, iterator.GetLength(hit));
-
- hit = iterator;
- hit.Seek(ch, byte.MaxValue);
- Assert.Equal(ch, iterator.GetLength(hit));
-
- hit = iterator;
- hit.Seek(byte.MaxValue, ch);
- Assert.Equal(ch, iterator.GetLength(hit));
-
- hit = iterator;
- hit.Seek(ch, byte.MaxValue, byte.MaxValue);
- Assert.Equal(ch, iterator.GetLength(hit));
-
- hit = iterator;
- hit.Seek(byte.MaxValue, ch, byte.MaxValue);
- Assert.Equal(ch, iterator.GetLength(hit));
-
- hit = iterator;
- hit.Seek(ch, byte.MaxValue, byte.MaxValue);
- Assert.Equal(ch, iterator.GetLength(hit));
- }
-
- pool.Return(block);
- }
- }
-
- [Fact]
- public void SeekWorksAcrossBlocks()
- {
- using (var pool = new MemoryPool())
- {
- var block1 = pool.Lease();
- var block2 = block1.Next = pool.Lease();
- var block3 = block2.Next = pool.Lease();
-
- foreach (var ch in Enumerable.Range(0, 34).Select(x => (byte)x))
- {
- block1.Array[block1.End++] = ch;
- }
- foreach (var ch in Enumerable.Range(34, 25).Select(x => (byte)x))
- {
- block2.Array[block2.End++] = ch;
- }
- foreach (var ch in Enumerable.Range(59, 197).Select(x => (byte)x))
- {
- block3.Array[block3.End++] = ch;
- }
-
- var iterator = block1.GetIterator();
- foreach (var ch in Enumerable.Range(0, 256).Select(x => (byte)x))
- {
- var hit = iterator;
- hit.Seek(ch);
- Assert.Equal(ch, iterator.GetLength(hit));
-
- hit = iterator;
- hit.Seek(ch, byte.MaxValue);
- Assert.Equal(ch, iterator.GetLength(hit));
-
- hit = iterator;
- hit.Seek(byte.MaxValue, ch);
- Assert.Equal(ch, iterator.GetLength(hit));
-
- hit = iterator;
- hit.Seek(ch, byte.MaxValue, byte.MaxValue);
- Assert.Equal(ch, iterator.GetLength(hit));
-
- hit = iterator;
- hit.Seek(byte.MaxValue, ch, byte.MaxValue);
- Assert.Equal(ch, iterator.GetLength(hit));
-
- hit = iterator;
- hit.Seek(byte.MaxValue, byte.MaxValue, ch);
- Assert.Equal(ch, iterator.GetLength(hit));
- }
-
- pool.Return(block1);
- pool.Return(block2);
- pool.Return(block3);
- }
- }
-
+
[Fact]
public void GetLengthBetweenIteratorsWorks()
{
@@ -192,87 +94,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
pool.Return(block2);
}
}
-
- [Fact]
- public void CopyToCorrectlyTraversesBlocks()
- {
- using (var pool = new MemoryPool())
- {
- var block1 = pool.Lease();
- var block2 = block1.Next = pool.Lease();
-
- for (int i = 0; i < 128; i++)
- {
- block1.Array[block1.End++] = (byte)i;
- }
- for (int i = 128; i < 256; i++)
- {
- block2.Array[block2.End++] = (byte)i;
- }
-
- var beginIterator = block1.GetIterator();
-
- var array = new byte[256];
- int actual;
- var endIterator = beginIterator.CopyTo(array, 0, 256, out actual);
-
- Assert.Equal(256, actual);
-
- for (int i = 0; i < 256; i++)
- {
- Assert.Equal(i, array[i]);
- }
-
- endIterator.CopyTo(array, 0, 256, out actual);
- Assert.Equal(0, actual);
-
- pool.Return(block1);
- pool.Return(block2);
- }
- }
-
- [Fact]
- public void CopyFromCorrectlyTraversesBlocks()
- {
- using (var pool = new MemoryPool())
- {
- var block1 = pool.Lease();
- var start = block1.GetIterator();
- var end = start;
- var bufferSize = block1.Data.Count * 3;
- var buffer = new byte[bufferSize];
-
- for (int i = 0; i < bufferSize; i++)
- {
- buffer[i] = (byte)(i % 73);
- }
-
- Assert.Null(block1.Next);
-
- end.CopyFrom(new ArraySegment(buffer));
-
- Assert.NotNull(block1.Next);
-
- for (int i = 0; i < bufferSize; i++)
- {
- Assert.Equal(i % 73, start.Take());
- }
-
- Assert.Equal(-1, start.Take());
- Assert.Equal(start.Block, end.Block);
- Assert.Equal(start.Index, end.Index);
-
- var block = block1;
- while (block != null)
- {
- var returnBlock = block;
- block = block.Next;
-
- pool.Return(returnBlock);
- }
- }
- }
-
+
[Fact]
public void IsEndCorrectlyTraversesBlocks()
{
diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolExtensions.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolExtensions.cs
index 6348258ce9..1d62dd2793 100644
--- a/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolExtensions.cs
+++ b/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolExtensions.cs
@@ -6,8 +6,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{
public static MemoryPoolIterator Add(this MemoryPoolIterator iterator, int count)
{
- int actual;
- return iterator.CopyTo(new byte[count], 0, count, out actual);
+ for (int i = 0; i < count; i++)
+ {
+ iterator.Take();
+ }
+ return iterator;
}
}
}
diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolIteratorTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolIteratorTests.cs
index 83cf36c4c8..4643b5236b 100644
--- a/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolIteratorTests.cs
+++ b/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolIteratorTests.cs
@@ -30,80 +30,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
_pool.Dispose();
}
- [Fact]
- public void TestFindFirstEqualByte()
- {
- var bytes = Enumerable.Repeat(0xff, Vector.Count).ToArray();
- for (int i = 0; i < Vector.Count; i++)
- {
- Vector vector = new Vector(bytes);
- Assert.Equal(i, MemoryPoolIterator.LocateFirstFoundByte(vector));
- bytes[i] = 0;
- }
-
- for (int i = 0; i < Vector.Count; i++)
- {
- bytes[i] = 1;
- Vector vector = new Vector(bytes);
- Assert.Equal(i, MemoryPoolIterator.LocateFirstFoundByte(vector));
- bytes[i] = 0;
- }
- }
-
- [Theory]
- [InlineData("a", "a", 'a', 0)]
- [InlineData("ab", "a", 'a', 0)]
- [InlineData("aab", "a", 'a', 0)]
- [InlineData("acab", "a", 'a', 0)]
- [InlineData("acab", "c", 'c', 1)]
- [InlineData("abcdefghijklmnopqrstuvwxyz", "lo", 'l', 11)]
- [InlineData("abcdefghijklmnopqrstuvwxyz", "ol", 'l', 11)]
- [InlineData("abcdefghijklmnopqrstuvwxyz", "ll", 'l', 11)]
- [InlineData("abcdefghijklmnopqrstuvwxyz", "lmr", 'l', 11)]
- [InlineData("abcdefghijklmnopqrstuvwxyz", "rml", 'l', 11)]
- [InlineData("abcdefghijklmnopqrstuvwxyz", "mlr", 'l', 11)]
- [InlineData("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", "lmr", 'l', 11)]
- [InlineData("aaaaaaaaaaalmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", "lmr", 'l', 11)]
- [InlineData("aaaaaaaaaaacmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", "lmr", 'm', 12)]
- [InlineData("aaaaaaaaaaarmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", "lmr", 'r', 11)]
- [InlineData("/localhost:5000/PATH/%2FPATH2/ HTTP/1.1", " %?", '%', 21)]
- [InlineData("/localhost:5000/PATH/%2FPATH2/?key=value HTTP/1.1", " %?", '%', 21)]
- [InlineData("/localhost:5000/PATH/PATH2/?key=value HTTP/1.1", " %?", '?', 27)]
- [InlineData("/localhost:5000/PATH/PATH2/ HTTP/1.1", " %?", ' ', 27)]
- public void MemorySeek(string raw, string search, char expectResult, int expectIndex)
- {
- var block = _pool.Lease();
- var chars = raw.ToCharArray().Select(c => (byte)c).ToArray();
- Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length);
- block.End += chars.Length;
-
- var begin = block.GetIterator();
- var searchFor = search.ToCharArray();
-
- int found = -1;
- if (searchFor.Length == 1)
- {
- found = begin.Seek((byte)searchFor[0]);
- }
- else if (searchFor.Length == 2)
- {
- found = begin.Seek((byte)searchFor[0], (byte)searchFor[1]);
- }
- else if (searchFor.Length == 3)
- {
- found = begin.Seek((byte)searchFor[0], (byte)searchFor[1], (byte)searchFor[2]);
- }
- else
- {
- Assert.False(true, "Invalid test sample.");
- }
-
- Assert.Equal(expectResult, found);
- Assert.Equal(expectIndex, begin.Index - block.Start);
-
- _pool.Return(block);
- }
-
[Fact]
public void Put()
{
@@ -241,728 +167,20 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
}
- [Fact]
- public void PeekLong()
- {
- // Arrange
- var block = _pool.Lease();
- var bytes = BitConverter.GetBytes(0x0102030405060708UL);
- Buffer.BlockCopy(bytes, 0, block.Array, block.Start, bytes.Length);
- block.End += bytes.Length;
- var scan = block.GetIterator();
- var originalIndex = scan.Index;
-
- // Assert
- ulong result;
- Assert.True(scan.TryPeekLong(out result));
- Assert.Equal(0x0102030405060708UL, result);
- Assert.Equal(originalIndex, scan.Index);
-
- _pool.Return(block);
- }
-
- [Theory]
- [InlineData(0)]
- [InlineData(1)]
- [InlineData(2)]
- [InlineData(3)]
- [InlineData(4)]
- [InlineData(5)]
- [InlineData(6)]
- [InlineData(7)]
- public void PeekLongNotEnoughBytes(int totalBytes)
- {
- // Arrange
- var block = _pool.Lease();
- var bytes = BitConverter.GetBytes(0x0102030405060708UL);
- var bytesLength = totalBytes;
- Buffer.BlockCopy(bytes, 0, block.Array, block.Start, bytesLength);
- block.End += bytesLength;
- var scan = block.GetIterator();
- var originalIndex = scan.Index;
-
- // Assert
- ulong result;
- Assert.False(scan.TryPeekLong(out result));
- Assert.Equal(originalIndex, scan.Index);
- _pool.Return(block);
- }
-
- [Theory]
- [InlineData(0)]
- [InlineData(1)]
- [InlineData(2)]
- [InlineData(3)]
- [InlineData(4)]
- [InlineData(5)]
- [InlineData(6)]
- [InlineData(7)]
- public void PeekLongNotEnoughBytesAtBlockBoundary(int firstBlockBytes)
- {
- // Arrange
- var expectedResult = 0x0102030405060708UL;
- var nextBlockBytes = 7 - firstBlockBytes;
-
- var block = _pool.Lease();
- block.End += firstBlockBytes;
-
- var nextBlock = _pool.Lease();
- nextBlock.End += nextBlockBytes;
-
- block.Next = nextBlock;
-
- var bytes = BitConverter.GetBytes(expectedResult);
- Buffer.BlockCopy(bytes, 0, block.Array, block.Start, firstBlockBytes);
- Buffer.BlockCopy(bytes, firstBlockBytes, nextBlock.Array, nextBlock.Start, nextBlockBytes);
-
- var scan = block.GetIterator();
- var originalIndex = scan.Index;
-
- // Assert
- ulong result;
- Assert.False(scan.TryPeekLong(out result));
- Assert.Equal(originalIndex, scan.Index);
-
- _pool.Return(block);
- _pool.Return(nextBlock);
- }
-
- [Theory]
- [InlineData(0)]
- [InlineData(1)]
- [InlineData(2)]
- [InlineData(3)]
- [InlineData(4)]
- [InlineData(5)]
- [InlineData(6)]
- [InlineData(7)]
- [InlineData(8)]
- public void PeekLongAtBlockBoundary(int firstBlockBytes)
- {
- // Arrange
- var expectedResult = 0x0102030405060708UL;
- var nonZeroData = 0xFF00FFFF0000FFFFUL;
- var nextBlockBytes = 8 - firstBlockBytes;
-
- var block = _pool.Lease();
- block.Start += 8;
- block.End = block.Start + firstBlockBytes;
-
- var nextBlock = _pool.Lease();
- nextBlock.Start += 8;
- nextBlock.End = nextBlock.Start + nextBlockBytes;
-
- block.Next = nextBlock;
-
- var bytes = BitConverter.GetBytes(expectedResult);
- Buffer.BlockCopy(bytes, 0, block.Array, block.Start, firstBlockBytes);
- Buffer.BlockCopy(bytes, firstBlockBytes, nextBlock.Array, nextBlock.Start, nextBlockBytes);
-
- // Fill in surrounding bytes with non-zero data
- var nonZeroBytes = BitConverter.GetBytes(nonZeroData);
- Buffer.BlockCopy(nonZeroBytes, 0, block.Array, block.Start - 8, 8);
- Buffer.BlockCopy(nonZeroBytes, 0, block.Array, block.End, 8);
- Buffer.BlockCopy(nonZeroBytes, 0, nextBlock.Array, nextBlock.Start - 8, 8);
- Buffer.BlockCopy(nonZeroBytes, 0, nextBlock.Array, nextBlock.End, 8);
-
- var scan = block.GetIterator();
- var originalIndex = scan.Index;
-
- // Assert
- ulong result;
- Assert.True(scan.TryPeekLong(out result));
- Assert.Equal(expectedResult, result);
- Assert.Equal(originalIndex, scan.Index);
-
- _pool.Return(block);
- _pool.Return(nextBlock);
- }
-
- [Theory]
- [InlineData(0)]
- [InlineData(1)]
- [InlineData(2)]
- [InlineData(3)]
- [InlineData(4)]
- [InlineData(5)]
- [InlineData(6)]
- [InlineData(7)]
- [InlineData(8)]
- public void PeekLongAtBlockBoundarayWithMostSignificatBitsSet(int firstBlockBytes)
- {
- // Arrange
- var expectedResult = 0xFF02030405060708UL;
- var nonZeroData = 0xFF00FFFF0000FFFFUL;
- var nextBlockBytes = 8 - firstBlockBytes;
-
- var block = _pool.Lease();
- block.Start += 8;
- block.End = block.Start + firstBlockBytes;
-
- var nextBlock = _pool.Lease();
- nextBlock.Start += 8;
- nextBlock.End = nextBlock.Start + nextBlockBytes;
-
- block.Next = nextBlock;
-
- var expectedBytes = BitConverter.GetBytes(expectedResult);
- Buffer.BlockCopy(expectedBytes, 0, block.Array, block.Start, firstBlockBytes);
- Buffer.BlockCopy(expectedBytes, firstBlockBytes, nextBlock.Array, nextBlock.Start, nextBlockBytes);
-
- // Fill in surrounding bytes with non-zero data
- var nonZeroBytes = BitConverter.GetBytes(nonZeroData);
- Buffer.BlockCopy(nonZeroBytes, 0, block.Array, block.Start - 8, 8);
- Buffer.BlockCopy(nonZeroBytes, 0, block.Array, block.End, 8);
- Buffer.BlockCopy(nonZeroBytes, 0, nextBlock.Array, nextBlock.Start - 8, 8);
- Buffer.BlockCopy(nonZeroBytes, 0, nextBlock.Array, nextBlock.End, 8);
-
- var scan = block.GetIterator();
- var originalIndex = scan.Index;
-
- // Assert
- ulong result;
- Assert.True(scan.TryPeekLong(out result));
- Assert.Equal(expectedResult, result);
- Assert.Equal(originalIndex, scan.Index);
-
- _pool.Return(block);
- _pool.Return(nextBlock);
- }
-
- [Theory]
- [InlineData(0)]
- [InlineData(1)]
- [InlineData(2)]
- [InlineData(3)]
- [InlineData(4)]
- [InlineData(5)]
- [InlineData(6)]
- [InlineData(7)]
- [InlineData(8)]
- [InlineData(9)]
- public void SkipAtBlockBoundary(int blockBytes)
- {
- // Arrange
- var nextBlockBytes = 10 - blockBytes;
-
- var block = _pool.Lease();
- block.End += blockBytes;
-
- var nextBlock = _pool.Lease();
- nextBlock.End += nextBlockBytes;
-
- block.Next = nextBlock;
-
- var bytes = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
- Buffer.BlockCopy(bytes, 0, block.Array, block.Start, blockBytes);
- Buffer.BlockCopy(bytes, blockBytes, nextBlock.Array, nextBlock.Start, nextBlockBytes);
-
- var scan = block.GetIterator();
- var originalIndex = scan.Index;
-
- // Act
- scan.Skip(8);
- var result = scan.Take();
-
- // Assert
- Assert.Equal(0x08, result);
- Assert.NotEqual(originalIndex, scan.Index);
-
- _pool.Return(block);
- _pool.Return(nextBlock);
- }
-
- [Fact]
- public void SkipThrowsWhenSkippingMoreBytesThanAvailableInSingleBlock()
- {
- // Arrange
- var block = _pool.Lease();
- block.End += 5;
-
- var scan = block.GetIterator();
-
- // Act/Assert
- Assert.ThrowsAny(() => scan.Skip(8));
-
- _pool.Return(block);
- }
-
- [Fact]
- public void SkipThrowsWhenSkippingMoreBytesThanAvailableInMultipleBlocks()
- {
- // Arrange
- var firstBlock = _pool.Lease();
- firstBlock.End += 3;
-
- var middleBlock = _pool.Lease();
- middleBlock.End += 1;
- firstBlock.Next = middleBlock;
-
- var finalBlock = _pool.Lease();
- finalBlock.End += 2;
- middleBlock.Next = finalBlock;
-
- var scan = firstBlock.GetIterator();
-
- // Act/Assert
- Assert.ThrowsAny(() => scan.Skip(8));
-
- _pool.Return(firstBlock);
- _pool.Return(middleBlock);
- _pool.Return(finalBlock);
- }
-
-
- [Theory]
- [MemberData(nameof(SeekByteLimitData))]
- public void TestSeekByteLimitWithinSameBlock(string input, char seek, int limit, int expectedBytesScanned, int expectedReturnValue)
- {
- MemoryPoolBlock block = null;
-
- try
- {
- // Arrange
-
- block = _pool.Lease();
- var chars = input.ToString().ToCharArray().Select(c => (byte) c).ToArray();
- Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length);
- block.End += chars.Length;
- var scan = block.GetIterator();
-
- // Act
- int bytesScanned;
- var returnValue = scan.Seek((byte)seek, out bytesScanned, limit);
-
- // Assert
- Assert.Equal(expectedBytesScanned, bytesScanned);
- Assert.Equal(expectedReturnValue, returnValue);
-
- Assert.Same(block, scan.Block);
- var expectedEndIndex = expectedReturnValue != -1 ?
- block.Start + input.IndexOf(seek) :
- block.Start + expectedBytesScanned;
- Assert.Equal(expectedEndIndex, scan.Index);
- }
- finally
- {
- // Cleanup
- if (block != null) _pool.Return(block);
- }
- }
-
- [Theory]
- [MemberData(nameof(SeekByteLimitData))]
- public void TestSeekByteLimitAcrossBlocks(string input, char seek, int limit, int expectedBytesScanned, int expectedReturnValue)
- {
- MemoryPoolBlock block1 = null;
- MemoryPoolBlock block2 = null;
- MemoryPoolBlock emptyBlock = null;
-
- try
- {
- // Arrange
- var input1 = input.Substring(0, input.Length / 2);
- block1 = _pool.Lease();
- var chars1 = input1.ToCharArray().Select(c => (byte)c).ToArray();
- Buffer.BlockCopy(chars1, 0, block1.Array, block1.Start, chars1.Length);
- block1.End += chars1.Length;
-
- emptyBlock = _pool.Lease();
- block1.Next = emptyBlock;
-
- var input2 = input.Substring(input.Length / 2);
- block2 = _pool.Lease();
- var chars2 = input2.ToCharArray().Select(c => (byte)c).ToArray();
- Buffer.BlockCopy(chars2, 0, block2.Array, block2.Start, chars2.Length);
- block2.End += chars2.Length;
- emptyBlock.Next = block2;
-
- var scan = block1.GetIterator();
-
- // Act
- int bytesScanned;
- var returnValue = scan.Seek((byte)seek, out bytesScanned, limit);
-
- // Assert
- Assert.Equal(expectedBytesScanned, bytesScanned);
- Assert.Equal(expectedReturnValue, returnValue);
-
- var seekCharIndex = input.IndexOf(seek);
- var expectedEndBlock = limit <= input.Length / 2 ?
- block1 :
- (seekCharIndex != -1 && seekCharIndex < input.Length / 2 ? block1 : block2);
- Assert.Same(expectedEndBlock, scan.Block);
- var expectedEndIndex = expectedReturnValue != -1 ?
- expectedEndBlock.Start + (expectedEndBlock == block1 ? input1.IndexOf(seek) : input2.IndexOf(seek)) :
- expectedEndBlock.Start + (expectedEndBlock == block1 ? expectedBytesScanned : expectedBytesScanned - (input.Length / 2));
- Assert.Equal(expectedEndIndex, scan.Index);
- }
- finally
- {
- // Cleanup
- if (block1 != null) _pool.Return(block1);
- if (emptyBlock != null) _pool.Return(emptyBlock);
- if (block2 != null) _pool.Return(block2);
- }
- }
-
- [Theory]
- [MemberData(nameof(SeekIteratorLimitData))]
- public void TestSeekIteratorLimitWithinSameBlock(string input, char seek, char limitAt, int expectedReturnValue)
- {
- MemoryPoolBlock block = null;
-
- try
- {
- // Arrange
- var afterSeek = (byte)'B';
-
- block = _pool.Lease();
- var chars = input.ToCharArray().Select(c => (byte)c).ToArray();
- Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length);
- block.End += chars.Length;
- var scan1 = block.GetIterator();
- var scan2_1 = scan1;
- var scan2_2 = scan1;
- var scan3_1 = scan1;
- var scan3_2 = scan1;
- var scan3_3 = scan1;
- var end = scan1;
-
- // Act
- var endReturnValue = end.Seek((byte)limitAt);
- var returnValue1 = scan1.Seek((byte)seek, ref end);
- var returnValue2_1 = scan2_1.Seek((byte)seek, afterSeek, ref end);
- var returnValue2_2 = scan2_2.Seek(afterSeek, (byte)seek, ref end);
- var returnValue3_1 = scan3_1.Seek((byte)seek, afterSeek, afterSeek, ref end);
- var returnValue3_2 = scan3_2.Seek(afterSeek, (byte)seek, afterSeek, ref end);
- var returnValue3_3 = scan3_3.Seek(afterSeek, afterSeek, (byte)seek, ref end);
-
- // Assert
- Assert.Equal(input.Contains(limitAt) ? limitAt : -1, endReturnValue);
- Assert.Equal(expectedReturnValue, returnValue1);
- Assert.Equal(expectedReturnValue, returnValue2_1);
- Assert.Equal(expectedReturnValue, returnValue2_2);
- Assert.Equal(expectedReturnValue, returnValue3_1);
- Assert.Equal(expectedReturnValue, returnValue3_2);
- Assert.Equal(expectedReturnValue, returnValue3_3);
-
- Assert.Same(block, scan1.Block);
- Assert.Same(block, scan2_1.Block);
- Assert.Same(block, scan2_2.Block);
- Assert.Same(block, scan3_1.Block);
- Assert.Same(block, scan3_2.Block);
- Assert.Same(block, scan3_3.Block);
-
- var expectedEndIndex = expectedReturnValue != -1 ? block.Start + input.IndexOf(seek) : end.Index;
- Assert.Equal(expectedEndIndex, scan1.Index);
- Assert.Equal(expectedEndIndex, scan2_1.Index);
- Assert.Equal(expectedEndIndex, scan2_2.Index);
- Assert.Equal(expectedEndIndex, scan3_1.Index);
- Assert.Equal(expectedEndIndex, scan3_2.Index);
- Assert.Equal(expectedEndIndex, scan3_3.Index);
- }
- finally
- {
- // Cleanup
- if (block != null) _pool.Return(block);
- }
- }
-
- [Theory]
- [MemberData(nameof(SeekIteratorLimitData))]
- public void TestSeekIteratorLimitAcrossBlocks(string input, char seek, char limitAt, int expectedReturnValue)
- {
- MemoryPoolBlock block1 = null;
- MemoryPoolBlock block2 = null;
- MemoryPoolBlock emptyBlock = null;
-
- try
- {
- // Arrange
- var afterSeek = (byte)'B';
-
- var input1 = input.Substring(0, input.Length / 2);
- block1 = _pool.Lease();
- var chars1 = input1.ToCharArray().Select(c => (byte)c).ToArray();
- Buffer.BlockCopy(chars1, 0, block1.Array, block1.Start, chars1.Length);
- block1.End += chars1.Length;
-
- emptyBlock = _pool.Lease();
- block1.Next = emptyBlock;
-
- var input2 = input.Substring(input.Length / 2);
- block2 = _pool.Lease();
- var chars2 = input2.ToCharArray().Select(c => (byte)c).ToArray();
- Buffer.BlockCopy(chars2, 0, block2.Array, block2.Start, chars2.Length);
- block2.End += chars2.Length;
- emptyBlock.Next = block2;
-
- var scan1 = block1.GetIterator();
- var scan2_1 = scan1;
- var scan2_2 = scan1;
- var scan3_1 = scan1;
- var scan3_2 = scan1;
- var scan3_3 = scan1;
- var end = scan1;
-
- // Act
- var endReturnValue = end.Seek((byte)limitAt);
- var returnValue1 = scan1.Seek((byte)seek, ref end);
- var returnValue2_1 = scan2_1.Seek((byte)seek, afterSeek, ref end);
- var returnValue2_2 = scan2_2.Seek(afterSeek, (byte)seek, ref end);
- var returnValue3_1 = scan3_1.Seek((byte)seek, afterSeek, afterSeek, ref end);
- var returnValue3_2 = scan3_2.Seek(afterSeek, (byte)seek, afterSeek, ref end);
- var returnValue3_3 = scan3_3.Seek(afterSeek, afterSeek, (byte)seek, ref end);
-
- // Assert
- Assert.Equal(input.Contains(limitAt) ? limitAt : -1, endReturnValue);
- Assert.Equal(expectedReturnValue, returnValue1);
- Assert.Equal(expectedReturnValue, returnValue2_1);
- Assert.Equal(expectedReturnValue, returnValue2_2);
- Assert.Equal(expectedReturnValue, returnValue3_1);
- Assert.Equal(expectedReturnValue, returnValue3_2);
- Assert.Equal(expectedReturnValue, returnValue3_3);
-
- var seekCharIndex = input.IndexOf(seek);
- var limitAtIndex = input.IndexOf(limitAt);
- var expectedEndBlock = seekCharIndex != -1 && seekCharIndex < input.Length / 2 ?
- block1 :
- (limitAtIndex != -1 && limitAtIndex < input.Length / 2 ? block1 : block2);
- Assert.Same(expectedEndBlock, scan1.Block);
- Assert.Same(expectedEndBlock, scan2_1.Block);
- Assert.Same(expectedEndBlock, scan2_2.Block);
- Assert.Same(expectedEndBlock, scan3_1.Block);
- Assert.Same(expectedEndBlock, scan3_2.Block);
- Assert.Same(expectedEndBlock, scan3_3.Block);
-
- var expectedEndIndex = expectedReturnValue != -1 ?
- expectedEndBlock.Start + (expectedEndBlock == block1 ? input1.IndexOf(seek) : input2.IndexOf(seek)) :
- end.Index;
- Assert.Equal(expectedEndIndex, scan1.Index);
- Assert.Equal(expectedEndIndex, scan2_1.Index);
- Assert.Equal(expectedEndIndex, scan2_2.Index);
- Assert.Equal(expectedEndIndex, scan3_1.Index);
- Assert.Equal(expectedEndIndex, scan3_2.Index);
- Assert.Equal(expectedEndIndex, scan3_3.Index);
- }
- finally
- {
- // Cleanup
- if (block1 != null) _pool.Return(block1);
- if (emptyBlock != null) _pool.Return(emptyBlock);
- if (block2 != null) _pool.Return(block2);
- }
- }
[Fact]
public void EmptyIteratorBehaviourIsValid()
{
const byte byteCr = (byte)'\n';
- ulong longValue;
var end = default(MemoryPoolIterator);
-
- Assert.False(default(MemoryPoolIterator).TryPeekLong(out longValue));
- Assert.Null(default(MemoryPoolIterator).GetAsciiString(ref end));
- Assert.Null(default(MemoryPoolIterator).GetUtf8String(ref end));
- // Assert.Equal doesn't work for default(ArraySegments)
- Assert.True(default(MemoryPoolIterator).GetArraySegment(end).Equals(default(ArraySegment)));
+
Assert.True(default(MemoryPoolIterator).IsDefault);
Assert.True(default(MemoryPoolIterator).IsEnd);
- Assert.Equal(default(MemoryPoolIterator).Take(), -1);
- Assert.Equal(default(MemoryPoolIterator).Peek(), -1);
- Assert.Equal(default(MemoryPoolIterator).Seek(byteCr), -1);
- Assert.Equal(default(MemoryPoolIterator).Seek(byteCr, ref end), -1);
- Assert.Equal(default(MemoryPoolIterator).Seek(byteCr, byteCr), -1);
- Assert.Equal(default(MemoryPoolIterator).Seek(byteCr, byteCr, byteCr), -1);
default(MemoryPoolIterator).CopyFrom(default(ArraySegment));
default(MemoryPoolIterator).CopyFromAscii("");
Assert.ThrowsAny(() => default(MemoryPoolIterator).Put(byteCr));
Assert.ThrowsAny(() => default(MemoryPoolIterator).GetLength(end));
- Assert.ThrowsAny(() => default(MemoryPoolIterator).Skip(1));
- }
-
- [Fact]
- public void TestGetArraySegment()
- {
- MemoryPoolBlock block0 = null;
- MemoryPoolBlock block1 = null;
-
- var byteRange = Enumerable.Range(1, 127).Select(x => (byte)x).ToArray();
- try
- {
- // Arrange
- block0 = _pool.Lease();
- block1 = _pool.Lease();
-
- block0.GetIterator().CopyFrom(byteRange);
- block1.GetIterator().CopyFrom(byteRange);
-
- block0.Next = block1;
-
- var begin = block0.GetIterator();
- var end0 = begin;
- var end1 = begin;
-
- end0.Skip(byteRange.Length);
- end1.Skip(byteRange.Length * 2);
-
- // Act
- var as0 = begin.GetArraySegment(end0);
- var as1 = begin.GetArraySegment(end1);
-
- // Assert
- Assert.Equal(as0.Count, byteRange.Length);
- Assert.Equal(as1.Count, byteRange.Length * 2);
-
- for (var i = 1; i < byteRange.Length; i++)
- {
- var asb0 = as0.Array[i + as0.Offset];
- var asb1 = as1.Array[i + as1.Offset];
- var b = byteRange[i];
-
- Assert.Equal(asb0, b);
- Assert.Equal(asb1, b);
- }
-
- for (var i = 1 + byteRange.Length; i < byteRange.Length * 2; i++)
- {
- var asb1 = as1.Array[i + as1.Offset];
- var b = byteRange[i - byteRange.Length];
-
- Assert.Equal(asb1, b);
- }
-
- }
- finally
- {
- if (block0 != null) _pool.Return(block0);
- if (block1 != null) _pool.Return(block1);
- }
- }
-
- [Fact]
- public void TestTake()
- {
- MemoryPoolBlock block0 = null;
- MemoryPoolBlock block1 = null;
- MemoryPoolBlock block2 = null;
- MemoryPoolBlock emptyBlock0 = null;
- MemoryPoolBlock emptyBlock1 = null;
-
- var byteRange = Enumerable.Range(1, 127).Select(x => (byte)x).ToArray();
- try
- {
- // Arrange
- block0 = _pool.Lease();
- block1 = _pool.Lease();
- block2 = _pool.Lease();
- emptyBlock0 = _pool.Lease();
- emptyBlock1 = _pool.Lease();
-
- block0.GetIterator().CopyFrom(byteRange);
- block1.GetIterator().CopyFrom(byteRange);
- block2.GetIterator().CopyFrom(byteRange);
-
- var begin = block0.GetIterator();
-
- // Single block
- for (var i = 0; i < byteRange.Length; i++)
- {
- var t = begin.Take();
- var b = byteRange[i];
-
- Assert.Equal(t, b);
- }
-
- Assert.Equal(begin.Take(), -1);
-
- // Dual block
- block0.Next = block1;
- begin = block0.GetIterator();
-
- for (var block = 0; block < 2; block++)
- {
- for (var i = 0; i < byteRange.Length; i++)
- {
- var t = begin.Take();
- var b = byteRange[i];
-
- Assert.Equal(t, b);
- }
- }
-
- Assert.Equal(begin.Take(), -1);
-
- // Multi block
- block1.Next = emptyBlock0;
- emptyBlock0.Next = emptyBlock1;
- emptyBlock1.Next = block2;
- begin = block0.GetIterator();
-
- for (var block = 0; block < 3; block++)
- {
- for (var i = 0; i < byteRange.Length; i++)
- {
- var t = begin.Take();
- var b = byteRange[i];
-
- Assert.Equal(t, b);
- }
- }
-
- Assert.Equal(begin.Take(), -1);
- }
- finally
- {
- if (block0 != null) _pool.Return(block0);
- if (block1 != null) _pool.Return(block1);
- if (block2 != null) _pool.Return(block2);
- if (emptyBlock0 != null) _pool.Return(emptyBlock0);
- if (emptyBlock1 != null) _pool.Return(emptyBlock1);
- }
- }
-
- [Fact]
- public void TestTakeEmptyBlocks()
- {
- MemoryPoolBlock emptyBlock0 = null;
- MemoryPoolBlock emptyBlock1 = null;
- MemoryPoolBlock emptyBlock2 = null;
- try
- {
- // Arrange
- emptyBlock0 = _pool.Lease();
- emptyBlock1 = _pool.Lease();
- emptyBlock2 = _pool.Lease();
-
- var beginEmpty = emptyBlock0.GetIterator();
-
- // Assert
-
- // No blocks
- Assert.Equal(default(MemoryPoolIterator).Take(), -1);
-
- // Single empty block
- Assert.Equal(beginEmpty.Take(), -1);
-
- // Dual empty block
- emptyBlock0.Next = emptyBlock1;
- beginEmpty = emptyBlock0.GetIterator();
- Assert.Equal(beginEmpty.Take(), -1);
-
- // Multi empty block
- emptyBlock1.Next = emptyBlock2;
- beginEmpty = emptyBlock0.GetIterator();
- Assert.Equal(beginEmpty.Take(), -1);
- }
- finally
- {
- if (emptyBlock0 != null) _pool.Return(emptyBlock0);
- if (emptyBlock1 != null) _pool.Return(emptyBlock1);
- if (emptyBlock2 != null) _pool.Return(emptyBlock2);
- }
}
[Theory]
@@ -973,23 +191,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("abcde", "abcde", 6)]
public void TestGetAsciiStringEscaped(string input, string expected, int maxChars)
{
- MemoryPoolBlock block = null;
-
- try
- {
// Arrange
- var buffer = new Span(Encoding.ASCII.GetBytes(input));
+ var buffer = new Span(Encoding.ASCII.GetBytes(input));
- // Act
- var result = buffer.GetAsciiStringEscaped(maxChars);
+ // Act
+ var result = buffer.GetAsciiStringEscaped(maxChars);
- // Assert
- Assert.Equal(expected, result);
- }
- finally
- {
- if (block != null) _pool.Return(block);
- }
+ // Assert
+ Assert.Equal(expected, result);
}
[Fact]
diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/UrlPathDecoder.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/UrlPathDecoder.cs
deleted file mode 100644
index 0acfc27fe8..0000000000
--- a/test/Microsoft.AspNetCore.Server.KestrelTests/UrlPathDecoder.cs
+++ /dev/null
@@ -1,226 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.Linq;
-using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
-using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
-using Xunit;
-
-namespace Microsoft.AspNetCore.Server.KestrelTests
-{
- public class UrlPathDecoderTests
- {
- [Fact]
- public void Empty()
- {
- using (var pool = new MemoryPool())
- {
- var mem = pool.Lease();
-
- PositiveAssert(mem, string.Empty, string.Empty);
-
- pool.Return(mem);
- }
- }
-
- [Fact]
- public void WhiteSpace()
- {
- using (var pool = new MemoryPool())
- {
- var mem = pool.Lease();
-
- PositiveAssert(mem, " ", " ");
-
- pool.Return(mem);
- }
- }
-
- [Theory]
- [InlineData("/foo/bar", "/foo/bar")]
- [InlineData("/foo/BAR", "/foo/BAR")]
- [InlineData("/foo/", "/foo/")]
- [InlineData("/", "/")]
- public void NormalCases(string raw, string expect)
- {
- using (var pool = new MemoryPool())
- {
- var mem = pool.Lease();
-
- PositiveAssert(mem, raw, expect);
-
- pool.Return(mem);
- }
- }
-
- [Theory]
- [InlineData("%2F", "%2F")]
- [InlineData("/foo%2Fbar", "/foo%2Fbar")]
- [InlineData("/foo%2F%20bar", "/foo%2F bar")]
- public void SkipForwardSlash(string raw, string expect)
- {
- using (var pool = new MemoryPool())
- {
- var mem = pool.Lease();
-
- PositiveAssert(mem, raw, expect);
-
- pool.Return(mem);
- }
- }
-
- [Theory]
- [InlineData("%D0%A4", "Ф")]
- [InlineData("%d0%a4", "Ф")]
- [InlineData("%E0%A4%AD", "भ")]
- [InlineData("%e0%A4%Ad", "भ")]
- [InlineData("%F0%A4%AD%A2", "𤭢")]
- [InlineData("%F0%a4%Ad%a2", "𤭢")]
- [InlineData("%48%65%6C%6C%6F%20%57%6F%72%6C%64", "Hello World")]
- [InlineData("%48%65%6C%6C%6F%2D%C2%B5%40%C3%9F%C3%B6%C3%A4%C3%BC%C3%A0%C3%A1", "Hello-µ@ßöäüàá")]
- // Test the borderline cases of overlong UTF8.
- [InlineData("%C2%80", "\u0080")]
- [InlineData("%E0%A0%80", "\u0800")]
- [InlineData("%F0%90%80%80", "\U00010000")]
- [InlineData("%63", "c")]
- [InlineData("%32", "2")]
- [InlineData("%20", " ")]
- public void ValidUTF8(string raw, string expect)
- {
- using (var pool = new MemoryPool())
- {
- var mem = pool.Lease();
-
- PositiveAssert(mem, raw, expect);
-
- pool.Return(mem);
- }
- }
-
- [Theory]
- [InlineData("%C3%84ra%20Benetton", "Ära Benetton")]
- [InlineData("%E6%88%91%E8%87%AA%E6%A8%AA%E5%88%80%E5%90%91%E5%A4%A9%E7%AC%91%E5%8E%BB%E7%95%99%E8%82%9D%E8%83%86%E4%B8%A4%E6%98%86%E4%BB%91", "我自横刀向天笑去留肝胆两昆仑")]
- public void Internationalized(string raw, string expect)
- {
- using (var pool = new MemoryPool())
- {
- var mem = pool.Lease();
-
- PositiveAssert(mem, raw, expect);
-
- pool.Return(mem);
- }
- }
-
- [Theory]
- // Overlong ASCII
- [InlineData("%C0%A4", "%C0%A4")]
- [InlineData("%C1%BF", "%C1%BF")]
- [InlineData("%E0%80%AF", "%E0%80%AF")]
- [InlineData("%E0%9F%BF", "%E0%9F%BF")]
- [InlineData("%F0%80%80%AF", "%F0%80%80%AF")]
- [InlineData("%F0%8F%8F%BF", "%F0%8F%8F%BF")]
- // Incomplete
- [InlineData("%", "%")]
- [InlineData("%%", "%%")]
- [InlineData("%A", "%A")]
- [InlineData("%Y", "%Y")]
- // Mixed
- [InlineData("%%32", "%2")]
- [InlineData("%%20", "% ")]
- [InlineData("%C0%A4%32", "%C0%A42")]
- [InlineData("%32%C0%A4%32", "2%C0%A42")]
- [InlineData("%C0%32%A4", "%C02%A4")]
- public void InvalidUTF8(string raw, string expect)
- {
- using (var pool = new MemoryPool())
- {
- var mem = pool.Lease();
-
- PositiveAssert(mem, raw, expect);
-
- pool.Return(mem);
- }
- }
-
- [Theory]
- [InlineData("/foo%2Fbar", 10, "/foo%2Fbar", 10)]
- [InlineData("/foo%2Fbar", 9, "/foo%2Fba", 9)]
- [InlineData("/foo%2Fbar", 8, "/foo%2Fb", 8)]
- [InlineData("%D0%A4", 6, "Ф", 1)]
- [InlineData("%D0%A4", 5, "%D0%A", 5)]
- [InlineData("%D0%A4", 4, "%D0%", 4)]
- [InlineData("%D0%A4", 3, "%D0", 3)]
- [InlineData("%D0%A4", 2, "%D", 2)]
- [InlineData("%D0%A4", 1, "%", 1)]
- [InlineData("%D0%A4", 0, "", 0)]
- [InlineData("%C2%B5%40%C3%9F%C3%B6%C3%A4%C3%BC%C3%A0%C3%A1", 45, "µ@ßöäüàá", 8)]
- [InlineData("%C2%B5%40%C3%9F%C3%B6%C3%A4%C3%BC%C3%A0%C3%A1", 44, "µ@ßöäüà%C3%A", 12)]
- public void DecodeWithBoundary(string raw, int rawLength, string expect, int expectLength)
- {
- using (var pool = new MemoryPool())
- {
- var mem = pool.Lease();
-
- var begin = BuildSample(mem, raw);
- var end = GetIterator(begin, rawLength);
-
- var end2 = UrlPathDecoder.Unescape(begin, end);
- var result = begin.GetUtf8String(ref end2);
-
- Assert.Equal(expectLength, result.Length);
- Assert.Equal(expect, result);
-
- pool.Return(mem);
- }
- }
-
- private MemoryPoolIterator BuildSample(MemoryPoolBlock mem, string data)
- {
- var store = data.Select(c => (byte)c).ToArray();
- mem.GetIterator().CopyFrom(new ArraySegment(store));
-
- return mem.GetIterator();
- }
-
- private MemoryPoolIterator GetIterator(MemoryPoolIterator begin, int displacement)
- {
- var result = begin;
- for (int i = 0; i < displacement; ++i)
- {
- result.Take();
- }
-
- return result;
- }
-
- private void PositiveAssert(MemoryPoolBlock mem, string raw, string expect)
- {
- var begin = BuildSample(mem, raw);
- var end = GetIterator(begin, raw.Length);
-
- var result = UrlPathDecoder.Unescape(begin, end);
- Assert.Equal(expect, begin.GetUtf8String(ref result));
- }
-
- private void PositiveAssert(MemoryPoolBlock mem, string raw)
- {
- var begin = BuildSample(mem, raw);
- var end = GetIterator(begin, raw.Length);
-
- var result = UrlPathDecoder.Unescape(begin, end);
- Assert.NotEqual(raw.Length, begin.GetUtf8String(ref result).Length);
- }
-
- private void NegativeAssert(MemoryPoolBlock mem, string raw)
- {
- var begin = BuildSample(mem, raw);
- var end = GetIterator(begin, raw.Length);
-
- var resultEnd = UrlPathDecoder.Unescape(begin, end);
- var result = begin.GetUtf8String(ref resultEnd);
- Assert.Equal(raw, result);
- }
- }
-}
diff --git a/test/shared/HttpParsingData.cs b/test/shared/HttpParsingData.cs
index b38afb96c5..7d84ba37b3 100644
--- a/test/shared/HttpParsingData.cs
+++ b/test/shared/HttpParsingData.cs
@@ -4,14 +4,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using Microsoft.AspNetCore.Server.Kestrel;
using Xunit;
namespace Microsoft.AspNetCore.Testing
{
public class HttpParsingData
{
- public static IEnumerable ValidRequestLineData
+ public static IEnumerable RequestLineValidData
{
get
{
@@ -20,7 +19,7 @@ namespace Microsoft.AspNetCore.Testing
"GET",
"CUSTOM",
};
- var targets = new[]
+ var paths = new[]
{
Tuple.Create("/", "/"),
Tuple.Create("/abc", "/abc"),
@@ -57,166 +56,154 @@ namespace Microsoft.AspNetCore.Testing
};
return from method in methods
- from target in targets
+ from path in paths
from queryString in queryStrings
from httpVersion in httpVersions
select new[]
{
- $"{method} {target.Item1}{queryString} {httpVersion}\r\n",
+ $"{method} {path.Item1}{queryString} {httpVersion}\r\n",
method,
- $"{target.Item2}",
+ $"{path.Item1}{queryString}",
+ $"{path.Item1}",
+ $"{path.Item2}",
queryString,
httpVersion
};
}
}
- // All these test cases must end in '\n', otherwise the server will spin forever
- public static IEnumerable InvalidRequestLineData
+ public static IEnumerable RequestLineIncompleteData => new[]
{
- get
- {
- var invalidRequestLines = new[]
- {
- "G\r\n",
- "GE\r\n",
- "GET\r\n",
- "GET \r\n",
- "GET /\r\n",
- "GET / \r\n",
- "GET/HTTP/1.1\r\n",
- "GET /HTTP/1.1\r\n",
- " \r\n",
- " \r\n",
- "/ HTTP/1.1\r\n",
- " / HTTP/1.1\r\n",
- "/ \r\n",
- "GET \r\n",
- "GET HTTP/1.0\r\n",
- "GET HTTP/1.1\r\n",
- "GET / \n",
- "GET / HTTP/1.0\n",
- "GET / HTTP/1.1\n",
- "GET / HTTP/1.0\rA\n",
- "GET / HTTP/1.1\ra\n",
- "GET? / HTTP/1.1\r\n",
- "GET ? HTTP/1.1\r\n",
- "GET /a?b=cHTTP/1.1\r\n",
- "GET /a%20bHTTP/1.1\r\n",
- "GET /a%20b?c=dHTTP/1.1\r\n",
- "GET %2F HTTP/1.1\r\n",
- "GET %00 HTTP/1.1\r\n",
- "CUSTOM \r\n",
- "CUSTOM /\r\n",
- "CUSTOM / \r\n",
- "CUSTOM /HTTP/1.1\r\n",
- "CUSTOM \r\n",
- "CUSTOM HTTP/1.0\r\n",
- "CUSTOM HTTP/1.1\r\n",
- "CUSTOM / \n",
- "CUSTOM / HTTP/1.0\n",
- "CUSTOM / HTTP/1.1\n",
- "CUSTOM / HTTP/1.0\rA\n",
- "CUSTOM / HTTP/1.1\ra\n",
- "CUSTOM ? HTTP/1.1\r\n",
- "CUSTOM /a?b=cHTTP/1.1\r\n",
- "CUSTOM /a%20bHTTP/1.1\r\n",
- "CUSTOM /a%20b?c=dHTTP/1.1\r\n",
- "CUSTOM %2F HTTP/1.1\r\n",
- "CUSTOM %00 HTTP/1.1\r\n",
- // Bad HTTP Methods (invalid according to RFC)
- "( / HTTP/1.0\r\n",
- ") / HTTP/1.0\r\n",
- "< / HTTP/1.0\r\n",
- "> / HTTP/1.0\r\n",
- "@ / HTTP/1.0\r\n",
- ", / HTTP/1.0\r\n",
- "; / HTTP/1.0\r\n",
- ": / HTTP/1.0\r\n",
- "\\ / HTTP/1.0\r\n",
- "\" / HTTP/1.0\r\n",
- "/ / HTTP/1.0\r\n",
- "[ / HTTP/1.0\r\n",
- "] / HTTP/1.0\r\n",
- "? / HTTP/1.0\r\n",
- "= / HTTP/1.0\r\n",
- "{ / HTTP/1.0\r\n",
- "} / HTTP/1.0\r\n",
- "get@ / HTTP/1.0\r\n",
- "post= / HTTP/1.0\r\n",
- };
+ "G",
+ "GE",
+ "GET",
+ "GET ",
+ "GET /",
+ "GET / ",
+ "GET / H",
+ "GET / HT",
+ "GET / HTT",
+ "GET / HTTP",
+ "GET / HTTP/",
+ "GET / HTTP/1",
+ "GET / HTTP/1.",
+ "GET / HTTP/1.1",
+ "GET / HTTP/1.1\r",
+ };
- var encodedNullCharInTargetRequestLines = new[]
- {
- "GET /%00 HTTP/1.1\r\n",
- "GET /%00%00 HTTP/1.1\r\n",
- "GET /%E8%00%84 HTTP/1.1\r\n",
- "GET /%E8%85%00 HTTP/1.1\r\n",
- "GET /%F3%00%82%86 HTTP/1.1\r\n",
- "GET /%F3%85%00%82 HTTP/1.1\r\n",
- "GET /%F3%85%82%00 HTTP/1.1\r\n",
- "GET /%E8%85%00 HTTP/1.1\r\n",
- "GET /%E8%01%00 HTTP/1.1\r\n",
- };
-
- var nullCharInTargetRequestLines = new[]
- {
- "GET \0 HTTP/1.1\r\n",
- "GET /\0 HTTP/1.1\r\n",
- "GET /\0\0 HTTP/1.1\r\n",
- "GET /%C8\0 HTTP/1.1\r\n",
- };
-
- return invalidRequestLines.Select(requestLine => new object[]
- {
- requestLine,
- typeof(BadHttpRequestException),
- $"Invalid request line: {requestLine.Replace("\r", "<0x0D>").Replace("\n", "<0x0A>")}"
- })
- .Concat(encodedNullCharInTargetRequestLines.Select(requestLine => new object[]
- {
- requestLine,
- typeof(InvalidOperationException),
- $"The path contains null characters."
- }))
- .Concat(nullCharInTargetRequestLines.Select(requestLine => new object[]
- {
- requestLine,
- typeof(InvalidOperationException),
- new InvalidOperationException().Message
- }));
- }
- }
-
- public static TheoryData UnrecognizedHttpVersionData
+ public static IEnumerable RequestLineInvalidData => new[]
{
- get
- {
- return new TheoryData
- {
- "H",
- "HT",
- "HTT",
- "HTTP",
- "HTTP/",
- "HTTP/1",
- "HTTP/1.",
- "http/1.0",
- "http/1.1",
- "HTTP/1.1 ",
- "HTTP/1.0a",
- "HTTP/1.0ab",
- "HTTP/1.1a",
- "HTTP/1.1ab",
- "HTTP/1.2",
- "HTTP/3.0",
- "hello",
- "8charact",
- };
- }
- }
+ "G\r\n",
+ "GE\r\n",
+ "GET\r\n",
+ "GET \r\n",
+ "GET /\r\n",
+ "GET / \r\n",
+ "GET/HTTP/1.1\r\n",
+ "GET /HTTP/1.1\r\n",
+ " \r\n",
+ " \r\n",
+ "/ HTTP/1.1\r\n",
+ " / HTTP/1.1\r\n",
+ "/ \r\n",
+ "GET \r\n",
+ "GET HTTP/1.0\r\n",
+ "GET HTTP/1.1\r\n",
+ "GET / \n",
+ "GET / HTTP/1.0\n",
+ "GET / HTTP/1.1\n",
+ "GET / HTTP/1.0\rA\n",
+ "GET / HTTP/1.1\ra\n",
+ "GET? / HTTP/1.1\r\n",
+ "GET ? HTTP/1.1\r\n",
+ "GET /a?b=cHTTP/1.1\r\n",
+ "GET /a%20bHTTP/1.1\r\n",
+ "GET /a%20b?c=dHTTP/1.1\r\n",
+ "GET %2F HTTP/1.1\r\n",
+ "GET %00 HTTP/1.1\r\n",
+ "CUSTOM \r\n",
+ "CUSTOM /\r\n",
+ "CUSTOM / \r\n",
+ "CUSTOM /HTTP/1.1\r\n",
+ "CUSTOM \r\n",
+ "CUSTOM HTTP/1.0\r\n",
+ "CUSTOM HTTP/1.1\r\n",
+ "CUSTOM / \n",
+ "CUSTOM / HTTP/1.0\n",
+ "CUSTOM / HTTP/1.1\n",
+ "CUSTOM / HTTP/1.0\rA\n",
+ "CUSTOM / HTTP/1.1\ra\n",
+ "CUSTOM ? HTTP/1.1\r\n",
+ "CUSTOM /a?b=cHTTP/1.1\r\n",
+ "CUSTOM /a%20bHTTP/1.1\r\n",
+ "CUSTOM /a%20b?c=dHTTP/1.1\r\n",
+ "CUSTOM %2F HTTP/1.1\r\n",
+ "CUSTOM %00 HTTP/1.1\r\n",
+ // Bad HTTP Methods (invalid according to RFC)
+ "( / HTTP/1.0\r\n",
+ ") / HTTP/1.0\r\n",
+ "< / HTTP/1.0\r\n",
+ "> / HTTP/1.0\r\n",
+ "@ / HTTP/1.0\r\n",
+ ", / HTTP/1.0\r\n",
+ "; / HTTP/1.0\r\n",
+ ": / HTTP/1.0\r\n",
+ "\\ / HTTP/1.0\r\n",
+ "\" / HTTP/1.0\r\n",
+ "/ / HTTP/1.0\r\n",
+ "[ / HTTP/1.0\r\n",
+ "] / HTTP/1.0\r\n",
+ "? / HTTP/1.0\r\n",
+ "= / HTTP/1.0\r\n",
+ "{ / HTTP/1.0\r\n",
+ "} / HTTP/1.0\r\n",
+ "get@ / HTTP/1.0\r\n",
+ "post= / HTTP/1.0\r\n",
+ };
- public static IEnumerable InvalidRequestHeaderData
+ public static IEnumerable RequestLineWithEncodedNullCharInTargetData => new[]
+ {
+ "GET /%00 HTTP/1.1\r\n",
+ "GET /%00%00 HTTP/1.1\r\n",
+ "GET /%E8%00%84 HTTP/1.1\r\n",
+ "GET /%E8%85%00 HTTP/1.1\r\n",
+ "GET /%F3%00%82%86 HTTP/1.1\r\n",
+ "GET /%F3%85%00%82 HTTP/1.1\r\n",
+ "GET /%F3%85%82%00 HTTP/1.1\r\n",
+ "GET /%E8%01%00 HTTP/1.1\r\n",
+ };
+
+ public static IEnumerable RequestLineWithNullCharInTargetData => new[]
+ {
+ "GET \0 HTTP/1.1\r\n",
+ "GET /\0 HTTP/1.1\r\n",
+ "GET /\0\0 HTTP/1.1\r\n",
+ "GET /%C8\0 HTTP/1.1\r\n",
+ };
+
+ public static TheoryData UnrecognizedHttpVersionData => new TheoryData
+ {
+ "H",
+ "HT",
+ "HTT",
+ "HTTP",
+ "HTTP/",
+ "HTTP/1",
+ "HTTP/1.",
+ "http/1.0",
+ "http/1.1",
+ "HTTP/1.1 ",
+ "HTTP/1.0a",
+ "HTTP/1.0ab",
+ "HTTP/1.1a",
+ "HTTP/1.1ab",
+ "HTTP/1.2",
+ "HTTP/3.0",
+ "hello",
+ "8charact",
+ };
+
+ public static IEnumerable RequestHeaderInvalidData
{
get
{
@@ -245,6 +232,14 @@ namespace Microsoft.AspNetCore.Testing
"Header-1: value1\rHeader-2: value2\r\n\r\n",
"Header-1: value1\r\nHeader-2: value2\r\r\n",
"Header-1: value1\r\nHeader-2: v\ralue2\r\n",
+ "Header-1: Value__\rVector16________Vector32\r\n",
+ "Header-1: Value___Vector16\r________Vector32\r\n",
+ "Header-1: Value___Vector16_______\rVector32\r\n",
+ "Header-1: Value___Vector16________Vector32\r\r\n",
+ "Header-1: Value___Vector16________Vector32_\r\r\n",
+ "Header-1: Value___Vector16________Vector32Value___Vector16_______\rVector32\r\n",
+ "Header-1: Value___Vector16________Vector32Value___Vector16________Vector32\r\r\n",
+ "Header-1: Value___Vector16________Vector32Value___Vector16________Vector32_\r\r\n",
};
// Missing colon
@@ -253,6 +248,7 @@ namespace Microsoft.AspNetCore.Testing
"Header-1 value1\r\n\r\n",
"Header-1 value1\r\nHeader-2: value2\r\n\r\n",
"Header-1: value1\r\nHeader-2 value2\r\n\r\n",
+ "\n"
};
// Starting with whitespace
@@ -269,9 +265,20 @@ namespace Microsoft.AspNetCore.Testing
{
"Header : value\r\n\r\n",
"Header\t: value\r\n\r\n",
+ "Header\r: value\r\n\r\n",
+ "Header_\rVector16: value\r\n\r\n",
+ "Header__Vector16\r: value\r\n\r\n",
+ "Header__Vector16_\r: value\r\n\r\n",
+ "Header_\rVector16________Vector32: value\r\n\r\n",
+ "Header__Vector16________Vector32\r: value\r\n\r\n",
+ "Header__Vector16________Vector32_\r: value\r\n\r\n",
+ "Header__Vector16________Vector32Header_\rVector16________Vector32: value\r\n\r\n",
+ "Header__Vector16________Vector32Header__Vector16________Vector32\r: value\r\n\r\n",
+ "Header__Vector16________Vector32Header__Vector16________Vector32_\r: value\r\n\r\n",
"Header 1: value1\r\nHeader-2: value2\r\n\r\n",
"Header 1 : value1\r\nHeader-2: value2\r\n\r\n",
"Header 1\t: value1\r\nHeader-2: value2\r\n\r\n",
+ "Header 1\r: value1\r\nHeader-2: value2\r\n\r\n",
"Header-1: value1\r\nHeader 2: value2\r\n\r\n",
"Header-1: value1\r\nHeader-2 : value2\r\n\r\n",
"Header-1: value1\r\nHeader-2\t: value2\r\n\r\n",
@@ -287,11 +294,11 @@ namespace Microsoft.AspNetCore.Testing
return new[]
{
- Tuple.Create(headersWithLineFolding,"Header value line folding not supported."),
- Tuple.Create(headersWithCRInValue,"Header value must not contain CR characters."),
- Tuple.Create(headersWithMissingColon,"No ':' character found in header line."),
- Tuple.Create(headersStartingWithWhitespace, "Header line must not start with whitespace."),
- Tuple.Create(headersWithWithspaceInName,"Whitespace is not allowed in header name."),
+ Tuple.Create(headersWithLineFolding, "Whitespace is not allowed in header name."),
+ Tuple.Create(headersWithCRInValue, "Header value must not contain CR characters."),
+ Tuple.Create(headersWithMissingColon, "No ':' character found in header line."),
+ Tuple.Create(headersStartingWithWhitespace, "Whitespace is not allowed in header name."),
+ Tuple.Create(headersWithWithspaceInName, "Whitespace is not allowed in header name."),
Tuple.Create(headersNotEndingInCrLfLine, "Headers corrupted, invalid header sequence.")
}
.SelectMany(t => t.Item1.Select(headers => new[] { headers, t.Item2 }));