Faster response Content-Length parsing.

This commit is contained in:
Cesar Blum Silveira 2016-10-13 15:05:54 -07:00
parent 78584799a4
commit fff0adeaaf
2 changed files with 103 additions and 36 deletions

View File

@ -233,16 +233,41 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
}
public static long ParseContentLength(StringValues value)
public static unsafe long ParseContentLength(StringValues value)
{
try
var input = value.ToString();
var parsed = 0L;
fixed (char* ptr = input)
{
return long.Parse(value, NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture);
}
catch (FormatException ex)
{
throw new InvalidOperationException("Content-Length value must be an integral number.", ex);
var ch = (ushort*)ptr;
var end = ch + input.Length;
if (ch == end)
{
ThrowInvalidContentLengthException(value);
}
ushort digit = 0;
while (ch < end && (digit = (ushort)(*ch - 0x30)) <= 9)
{
parsed *= 10;
parsed += digit;
ch++;
}
if (ch != end)
{
ThrowInvalidContentLengthException(value);
}
}
return parsed;
}
private static void ThrowInvalidContentLengthException(string value)
{
throw new InvalidOperationException($"Invalid Content-Length: \"{value}\". Value must be a positive integral number.");
}
private static void ThrowInvalidHeaderCharacter(char ch)

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel;
@ -149,75 +150,88 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
Assert.Throws<InvalidOperationException>(() => dictionary.Clear());
}
[Fact]
public void ThrowsWhenAddingContentLengthWithNonNumericValue()
[Theory]
[MemberData(nameof(BadContentLengths))]
public void ThrowsWhenAddingContentLengthWithNonNumericValue(string contentLength)
{
var headers = new FrameResponseHeaders();
var dictionary = (IDictionary<string, StringValues>)headers;
Assert.Throws<InvalidOperationException>(() => dictionary.Add("Content-Length", new[] { "bad" }));
var exception = Assert.Throws<InvalidOperationException>(() => dictionary.Add("Content-Length", new[] { contentLength }));
Assert.Equal($"Invalid Content-Length: \"{contentLength}\". Value must be a positive integral number.", exception.Message);
}
[Fact]
public void ThrowsWhenSettingContentLengthToNonNumericValue()
[Theory]
[MemberData(nameof(BadContentLengths))]
public void ThrowsWhenSettingContentLengthToNonNumericValue(string contentLength)
{
var headers = new FrameResponseHeaders();
var dictionary = (IDictionary<string, StringValues>)headers;
Assert.Throws<InvalidOperationException>(() => ((IHeaderDictionary)headers)["Content-Length"] = "bad");
var exception = Assert.Throws<InvalidOperationException>(() => ((IHeaderDictionary)headers)["Content-Length"] = contentLength);
Assert.Equal($"Invalid Content-Length: \"{contentLength}\". Value must be a positive integral number.", exception.Message);
}
[Fact]
public void ThrowsWhenSettingRawContentLengthToNonNumericValue()
[Theory]
[MemberData(nameof(BadContentLengths))]
public void ThrowsWhenSettingRawContentLengthToNonNumericValue(string contentLength)
{
var headers = new FrameResponseHeaders();
Assert.Throws<InvalidOperationException>(() => headers.SetRawContentLength("bad", Encoding.ASCII.GetBytes("bad")));
var exception = Assert.Throws<InvalidOperationException>(() => headers.SetRawContentLength(contentLength, Encoding.ASCII.GetBytes(contentLength)));
Assert.Equal($"Invalid Content-Length: \"{contentLength}\". Value must be a positive integral number.", exception.Message);
}
[Fact]
public void ThrowsWhenAssigningHeaderContentLengthToNonNumericValue()
[Theory]
[MemberData(nameof(BadContentLengths))]
public void ThrowsWhenAssigningHeaderContentLengthToNonNumericValue(string contentLength)
{
var headers = new FrameResponseHeaders();
Assert.Throws<InvalidOperationException>(() => headers.HeaderContentLength = "bad");
var exception = Assert.Throws<InvalidOperationException>(() => headers.HeaderContentLength = contentLength);
Assert.Equal($"Invalid Content-Length: \"{contentLength}\". Value must be a positive integral number.", exception.Message);
}
[Fact]
public void ContentLengthValueCanBeReadAsLongAfterAddingHeader()
[Theory]
[MemberData(nameof(GoodContentLengths))]
public void ContentLengthValueCanBeReadAsLongAfterAddingHeader(string contentLength)
{
var headers = new FrameResponseHeaders();
var dictionary = (IDictionary<string, StringValues>)headers;
dictionary.Add("Content-Length", "42");
dictionary.Add("Content-Length", contentLength);
Assert.Equal(42, headers.HeaderContentLengthValue);
Assert.Equal(ParseLong(contentLength), headers.HeaderContentLengthValue);
}
[Fact]
public void ContentLengthValueCanBeReadAsLongAfterSettingHeader()
[Theory]
[MemberData(nameof(GoodContentLengths))]
public void ContentLengthValueCanBeReadAsLongAfterSettingHeader(string contentLength)
{
var headers = new FrameResponseHeaders();
var dictionary = (IDictionary<string, StringValues>)headers;
dictionary["Content-Length"] = "42";
dictionary["Content-Length"] = contentLength;
Assert.Equal(42, headers.HeaderContentLengthValue);
Assert.Equal(ParseLong(contentLength), headers.HeaderContentLengthValue);
}
[Fact]
public void ContentLengthValueCanBeReadAsLongAfterSettingRawHeader()
[Theory]
[MemberData(nameof(GoodContentLengths))]
public void ContentLengthValueCanBeReadAsLongAfterSettingRawHeader(string contentLength)
{
var headers = new FrameResponseHeaders();
headers.SetRawContentLength("42", Encoding.ASCII.GetBytes("42"));
headers.SetRawContentLength(contentLength, Encoding.ASCII.GetBytes(contentLength));
Assert.Equal(42, headers.HeaderContentLengthValue);
Assert.Equal(ParseLong(contentLength), headers.HeaderContentLengthValue);
}
[Fact]
public void ContentLengthValueCanBeReadAsLongAfterAssigningHeader()
[Theory]
[MemberData(nameof(GoodContentLengths))]
public void ContentLengthValueCanBeReadAsLongAfterAssigningHeader(string contentLength)
{
var headers = new FrameResponseHeaders();
headers.HeaderContentLength = "42";
headers.HeaderContentLength = contentLength;
Assert.Equal(42, headers.HeaderContentLengthValue);
Assert.Equal(ParseLong(contentLength), headers.HeaderContentLengthValue);
}
[Fact]
@ -243,5 +257,33 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
Assert.Equal(null, headers.HeaderContentLengthValue);
}
private static long ParseLong(string value)
{
return long.Parse(value, NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture);
}
public static TheoryData<string> GoodContentLengths => new TheoryData<string>
{
"0",
"00",
"042",
"42",
long.MaxValue.ToString(CultureInfo.InvariantCulture)
};
public static TheoryData<string> BadContentLengths => new TheoryData<string>
{
"",
" ",
" 42",
"42 ",
"bad",
"!",
"!42",
"42!",
"42,000",
"42.000",
};
}
}
}