Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

[NoMerge] For davidfowl #1320

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2c5dd5f
Use ContentLength as primary data source
benaadams Jan 20, 2017
bc2cb10
Zero contentLength msgbody doesn't need state
benaadams Jan 20, 2017
e2615b2
Remove HasContentLength prop
benaadams Jan 20, 2017
6a0c922
Throw correct exception, FormatInt64, test
benaadams Jan 20, 2017
4718d29
Add extra contentlength tests
benaadams Jan 21, 2017
421d1a1
Throw multiple content lengths
benaadams Jan 21, 2017
85a6655
Cleanup headers
benaadams Jan 21, 2017
55515dd
Headers Cleanup
benaadams Jan 21, 2017
e330a9c
Cut Append/AppendNonPrimaryHeaders zeroed stack
benaadams Jan 21, 2017
99c9012
Stop benchmarks crashing
benaadams Jan 22, 2017
fce9a79
Reduce TakeMessageHeaders stack usage
benaadams Jan 22, 2017
7ae0e0d
Faster CopyFromAscii
benaadams Jan 14, 2017
1d14ae1
Share Single and Multi impl; add 32bit
benaadams Jan 20, 2017
55e5a56
Increase test range
benaadams Jan 20, 2017
66f7549
Add ResponseHeaders benchmark
benaadams Jan 22, 2017
50c5afa
Vectorize ValidateHeaderCharacters
benaadams Jan 10, 2017
0d11a89
Subtract don't add, avoid overflow
benaadams Jan 20, 2017
04229be
Fast ulong output
benaadams Jan 23, 2017
ed66e0e
Use real world headers
benaadams Jan 22, 2017
4b57d2c
Cleanup KnownHeaders
benaadams Jan 23, 2017
1a3f4bf
Test before set
benaadams Jan 23, 2017
6333f36
Clear test before set, use local var
benaadams Jan 23, 2017
a1bfe6b
Feedback
benaadams Jan 23, 2017
06d1437
coreclr feedback
benaadams Jan 23, 2017
a30303d
Merge remote-tracking branch 'refs/remotes/origin/contentlength' into…
benaadams Jan 23, 2017
407d546
Merge remote-tracking branch 'refs/remotes/origin/TakeMessageHeaders'…
benaadams Jan 23, 2017
a5a7c30
Merge remote-tracking branch 'refs/remotes/origin/vector-perf' into f…
benaadams Jan 23, 2017
4b63cd0
Merge remote-tracking branch 'refs/remotes/origin/CopyFromAscii' into…
benaadams Jan 23, 2017
d7bc462
Lost in merge
benaadams Jan 23, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ internal static BadHttpRequestException GetException(RequestRejectionReason reas
case RequestRejectionReason.MalformedRequestInvalidHeaders:
ex = new BadHttpRequestException("Malformed request: invalid headers.", StatusCodes.Status400BadRequest);
break;
case RequestRejectionReason.MultipleContentLengths:
ex = new BadHttpRequestException("Multiple Content-Length headers.", StatusCodes.Status400BadRequest);
break;
case RequestRejectionReason.UnexpectedEndOfRequestContent:
ex = new BadHttpRequestException("Unexpected end of request content.", StatusCodes.Status400BadRequest);
break;
Expand Down
137 changes: 76 additions & 61 deletions src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ public abstract partial class Frame : IFrameControl
private static readonly byte[] _bytesConnectionKeepAlive = Encoding.ASCII.GetBytes("\r\nConnection: keep-alive");
private static readonly byte[] _bytesTransferEncodingChunked = Encoding.ASCII.GetBytes("\r\nTransfer-Encoding: chunked");
private static readonly byte[] _bytesHttpVersion11 = Encoding.ASCII.GetBytes("HTTP/1.1 ");
private static readonly byte[] _bytesContentLengthZero = Encoding.ASCII.GetBytes("\r\nContent-Length: 0");
private static readonly byte[] _bytesEndHeaders = Encoding.ASCII.GetBytes("\r\n\r\n");
private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: Kestrel");

Expand Down Expand Up @@ -657,12 +656,12 @@ private void VerifyAndUpdateWrite(int count)

if (responseHeaders != null &&
!responseHeaders.HasTransferEncoding &&
responseHeaders.HasContentLength &&
_responseBytesWritten + count > responseHeaders.HeaderContentLengthValue.Value)
responseHeaders.ContentLength.HasValue &&
_responseBytesWritten + count > responseHeaders.ContentLength.Value)
{
_keepAlive = false;
throw new InvalidOperationException(
$"Response Content-Length mismatch: too many bytes written ({_responseBytesWritten + count} of {responseHeaders.HeaderContentLengthValue.Value}).");
$"Response Content-Length mismatch: too many bytes written ({_responseBytesWritten + count} of {responseHeaders.ContentLength.Value}).");
}

_responseBytesWritten += count;
Expand All @@ -679,8 +678,8 @@ private void CheckLastWrite()
// Called after VerifyAndUpdateWrite(), so _responseBytesWritten has already been updated.
if (responseHeaders != null &&
!responseHeaders.HasTransferEncoding &&
responseHeaders.HasContentLength &&
_responseBytesWritten == responseHeaders.HeaderContentLengthValue.Value)
responseHeaders.ContentLength.HasValue &&
_responseBytesWritten == responseHeaders.ContentLength.Value)
{
_abortedCts = null;
}
Expand All @@ -692,8 +691,8 @@ protected void VerifyResponseContentLength()

if (!HttpMethods.IsHead(Method) &&
!responseHeaders.HasTransferEncoding &&
responseHeaders.HeaderContentLengthValue.HasValue &&
_responseBytesWritten < responseHeaders.HeaderContentLengthValue.Value)
responseHeaders.ContentLength.HasValue &&
_responseBytesWritten < responseHeaders.ContentLength.Value)
{
// We need to close the connection if any bytes were written since the client
// cannot be certain of how many bytes it will receive.
Expand All @@ -703,7 +702,7 @@ protected void VerifyResponseContentLength()
}

ReportApplicationError(new InvalidOperationException(
$"Response Content-Length mismatch: too few bytes written ({_responseBytesWritten} of {responseHeaders.HeaderContentLengthValue.Value})."));
$"Response Content-Length mismatch: too few bytes written ({_responseBytesWritten} of {responseHeaders.ContentLength.Value})."));
}
}

Expand Down Expand Up @@ -920,13 +919,13 @@ private void CreateResponseHeader(
// automatically for HEAD requests or 204, 205, 304 responses.
if (_canHaveBody)
{
if (!hasTransferEncoding && !responseHeaders.HasContentLength)
if (!hasTransferEncoding && !responseHeaders.ContentLength.HasValue)
{
if (appCompleted && StatusCode != StatusCodes.Status101SwitchingProtocols)
{
// Since the app has completed and we are only now generating
// the headers we can safely set the Content-Length to 0.
responseHeaders.SetRawContentLength("0", _bytesContentLengthZero);
responseHeaders.ContentLength = 0;
}
else
{
Expand Down Expand Up @@ -1258,9 +1257,9 @@ private bool RequestUrlStartsWithPathBase(string requestUrl, out bool caseMatche

public bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders requestHeaders)
{
var scan = input.ConsumingStart();
var consumed = scan;
var end = scan;
var end = input.ConsumingStart();
var consumed = end;
var remainingBytesAllowed = _remainingRequestHeadersBytesAllowed;
try
{
while (!end.IsEnd)
Expand Down Expand Up @@ -1303,9 +1302,9 @@ public bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders requestHea
}

int bytesScanned;
if (end.Seek(ByteLF, out bytesScanned, _remainingRequestHeadersBytesAllowed) == -1)
if (end.Seek(ByteCR, out bytesScanned, remainingBytesAllowed) == -1)
{
if (bytesScanned >= _remainingRequestHeadersBytesAllowed)
if (bytesScanned >= remainingBytesAllowed)
{
RejectRequest(RequestRejectionReason.HeadersExceedMaxTotalSize);
}
Expand All @@ -1315,51 +1314,35 @@ public bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders requestHea
}
}

var beginName = scan;
if (scan.Seek(ByteColon, ref end) == -1)
{
RejectRequest(RequestRejectionReason.NoColonCharacterFoundInHeaderLine);
}
var endName = scan;

scan.Take();

var validateName = beginName;
if (validateName.Seek(ByteSpace, ByteTab, ref endName) != -1)
{
RejectRequest(RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName);
}

var beginValue = scan;
ch = scan.Take();

while (ch == ByteSpace || ch == ByteTab)
var validate = end;
validate.Take();
ch = validate.Peek();
if (ch == -1)
{
beginValue = scan;
ch = scan.Take();
end = validate;
return false;
}

scan = beginValue;
if (scan.Seek(ByteCR, ref end) == -1)
else if (ch == ByteLF && bytesScanned + 1 > remainingBytesAllowed)
{
RejectRequest(RequestRejectionReason.MissingCRInHeaderLine);
end = validate;
RejectRequest(RequestRejectionReason.HeadersExceedMaxTotalSize);
}

scan.Take(); // we know this is '\r'
ch = scan.Take(); // expecting '\n'
end = scan;

if (ch != ByteLF)
else if (ch != ByteLF)
{
RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR);
}

var next = scan.Peek();
if (next == -1)
bytesScanned++;
remainingBytesAllowed -= bytesScanned;

validate.Take();
ch = validate.Peek();
if (ch == -1)
{
end = validate;
return false;
}
else if (next == ByteSpace || next == ByteTab)
else if (ch == ByteSpace || ch == ByteTab)
{
// From https://tools.ietf.org/html/rfc7230#section-3.2.4:
//
Expand All @@ -1381,34 +1364,66 @@ public bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders requestHea
RejectRequest(RequestRejectionReason.HeaderValueLineFoldingNotSupported);
}

var scan = consumed;
if (scan.Seek(ByteColon, ref end) == -1)
{
RejectRequest(RequestRejectionReason.NoColonCharacterFoundInHeaderLine);
}

validate = consumed;
if (validate.Seek(ByteSpace, ByteTab, ByteLF, ref scan) != -1)
{
if (validate.Peek() == ByteLF)
{
RejectRequest(RequestRejectionReason.MissingCRInHeaderLine);
}
RejectRequest(RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName);
}

var name = consumed.GetArraySegment(validate);
scan.Take();

ch = scan.Peek();

// Skip starting whitespace
while (ch == ByteSpace || ch == ByteTab)
{
scan.Take();
ch = scan.Peek();
}

// Trim trailing whitespace from header value by repeatedly advancing to next
// whitespace or CR.
//
// - If CR is found, this is the end of the header value.
// - If whitespace is found, this is the _tentative_ end of the header value.
// If non-whitespace is found after it and it's not CR, seek again to the next
// whitespace or CR for a new (possibly tentative) end of value.
var ws = beginValue;
var endValue = scan;

validate = end;
consumed = scan;
do
{
ws.Seek(ByteSpace, ByteTab, ByteCR);
endValue = ws;
consumed.Seek(ByteSpace, ByteTab, ByteCR);
validate = consumed;

ch = ws.Take();
ch = consumed.Peek();
while (ch == ByteSpace || ch == ByteTab)
{
ch = ws.Take();
consumed.Take();
ch = consumed.Peek();
}
} while (ch != ByteCR);

var name = beginName.GetArraySegment(endName);
var value = beginValue.GetAsciiString(ref endValue);
var value = scan.GetAsciiString(ref validate);

end.Take(); // CR
end.Take(); // LF
consumed = end;

consumed = scan;
requestHeaders.Append(name.Array, name.Offset, name.Count, value);

_remainingRequestHeadersBytesAllowed -= bytesScanned;
_remainingRequestHeadersBytesAllowed = remainingBytesAllowed;
_requestHeadersParsed++;
}

Expand Down Expand Up @@ -1468,7 +1483,7 @@ private void SetErrorResponseHeaders(int statusCode)
var dateHeaderValues = DateHeaderValueManager.GetDateHeaderValues();

responseHeaders.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes);
responseHeaders.SetRawContentLength("0", _bytesContentLengthZero);
responseHeaders.ContentLength = 0;

if (ServerOptions.AddServerHeader)
{
Expand Down
Loading