Faster response Content-Length parsing.
This commit is contained in:
parent
78584799a4
commit
fff0adeaaf
|
|
@ -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);
|
var ch = (ushort*)ptr;
|
||||||
}
|
var end = ch + input.Length;
|
||||||
catch (FormatException ex)
|
|
||||||
{
|
if (ch == end)
|
||||||
throw new InvalidOperationException("Content-Length value must be an integral number.", ex);
|
{
|
||||||
|
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)
|
private static void ThrowInvalidHeaderCharacter(char ch)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel;
|
using Microsoft.AspNetCore.Server.Kestrel;
|
||||||
|
|
@ -149,75 +150,88 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
Assert.Throws<InvalidOperationException>(() => dictionary.Clear());
|
Assert.Throws<InvalidOperationException>(() => dictionary.Clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Theory]
|
||||||
public void ThrowsWhenAddingContentLengthWithNonNumericValue()
|
[MemberData(nameof(BadContentLengths))]
|
||||||
|
public void ThrowsWhenAddingContentLengthWithNonNumericValue(string contentLength)
|
||||||
{
|
{
|
||||||
var headers = new FrameResponseHeaders();
|
var headers = new FrameResponseHeaders();
|
||||||
var dictionary = (IDictionary<string, StringValues>)headers;
|
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]
|
[Theory]
|
||||||
public void ThrowsWhenSettingContentLengthToNonNumericValue()
|
[MemberData(nameof(BadContentLengths))]
|
||||||
|
public void ThrowsWhenSettingContentLengthToNonNumericValue(string contentLength)
|
||||||
{
|
{
|
||||||
var headers = new FrameResponseHeaders();
|
var headers = new FrameResponseHeaders();
|
||||||
var dictionary = (IDictionary<string, StringValues>)headers;
|
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]
|
[Theory]
|
||||||
public void ThrowsWhenSettingRawContentLengthToNonNumericValue()
|
[MemberData(nameof(BadContentLengths))]
|
||||||
|
public void ThrowsWhenSettingRawContentLengthToNonNumericValue(string contentLength)
|
||||||
{
|
{
|
||||||
var headers = new FrameResponseHeaders();
|
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]
|
[Theory]
|
||||||
public void ThrowsWhenAssigningHeaderContentLengthToNonNumericValue()
|
[MemberData(nameof(BadContentLengths))]
|
||||||
|
public void ThrowsWhenAssigningHeaderContentLengthToNonNumericValue(string contentLength)
|
||||||
{
|
{
|
||||||
var headers = new FrameResponseHeaders();
|
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]
|
[Theory]
|
||||||
public void ContentLengthValueCanBeReadAsLongAfterAddingHeader()
|
[MemberData(nameof(GoodContentLengths))]
|
||||||
|
public void ContentLengthValueCanBeReadAsLongAfterAddingHeader(string contentLength)
|
||||||
{
|
{
|
||||||
var headers = new FrameResponseHeaders();
|
var headers = new FrameResponseHeaders();
|
||||||
var dictionary = (IDictionary<string, StringValues>)headers;
|
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]
|
[Theory]
|
||||||
public void ContentLengthValueCanBeReadAsLongAfterSettingHeader()
|
[MemberData(nameof(GoodContentLengths))]
|
||||||
|
public void ContentLengthValueCanBeReadAsLongAfterSettingHeader(string contentLength)
|
||||||
{
|
{
|
||||||
var headers = new FrameResponseHeaders();
|
var headers = new FrameResponseHeaders();
|
||||||
var dictionary = (IDictionary<string, StringValues>)headers;
|
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]
|
[Theory]
|
||||||
public void ContentLengthValueCanBeReadAsLongAfterSettingRawHeader()
|
[MemberData(nameof(GoodContentLengths))]
|
||||||
|
public void ContentLengthValueCanBeReadAsLongAfterSettingRawHeader(string contentLength)
|
||||||
{
|
{
|
||||||
var headers = new FrameResponseHeaders();
|
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]
|
[Theory]
|
||||||
public void ContentLengthValueCanBeReadAsLongAfterAssigningHeader()
|
[MemberData(nameof(GoodContentLengths))]
|
||||||
|
public void ContentLengthValueCanBeReadAsLongAfterAssigningHeader(string contentLength)
|
||||||
{
|
{
|
||||||
var headers = new FrameResponseHeaders();
|
var headers = new FrameResponseHeaders();
|
||||||
headers.HeaderContentLength = "42";
|
headers.HeaderContentLength = contentLength;
|
||||||
|
|
||||||
Assert.Equal(42, headers.HeaderContentLengthValue);
|
Assert.Equal(ParseLong(contentLength), headers.HeaderContentLengthValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -243,5 +257,33 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
|
|
||||||
Assert.Equal(null, headers.HeaderContentLengthValue);
|
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",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue