Remove allocations and improve Header Quality Values parsing performance
This commit is contained in:
parent
81bec95b6a
commit
7440d5d29c
|
|
@ -13,6 +13,7 @@ namespace Microsoft.Net.Http.Headers
|
||||||
public static class HeaderUtilities
|
public static class HeaderUtilities
|
||||||
{
|
{
|
||||||
private static readonly int _int64MaxStringLength = 19;
|
private static readonly int _int64MaxStringLength = 19;
|
||||||
|
private static readonly int _qualityValueMaxCharCount = 10; // Little bit more permissive than RFC7231 5.3.1
|
||||||
private const string QualityName = "q";
|
private const string QualityName = "q";
|
||||||
internal const string BytesUnit = "bytes";
|
internal const string BytesUnit = "bytes";
|
||||||
|
|
||||||
|
|
@ -62,9 +63,8 @@ namespace Microsoft.Net.Http.Headers
|
||||||
{
|
{
|
||||||
// Note that the RFC requires decimal '.' regardless of the culture. I.e. using ',' as decimal
|
// Note that the RFC requires decimal '.' regardless of the culture. I.e. using ',' as decimal
|
||||||
// separator is considered invalid (even if the current culture would allow it).
|
// separator is considered invalid (even if the current culture would allow it).
|
||||||
double qualityValue;
|
if (TryParseQualityDouble(qualityParameter.Value, 0, out var qualityValue, out var length))
|
||||||
if (double.TryParse(qualityParameter.Value, NumberStyles.AllowDecimalPoint,
|
|
||||||
NumberFormatInfo.InvariantInfo, out qualityValue))
|
|
||||||
{
|
{
|
||||||
return qualityValue;
|
return qualityValue;
|
||||||
}
|
}
|
||||||
|
|
@ -480,6 +480,95 @@ namespace Microsoft.Net.Http.Headers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strict and fast RFC7231 5.3.1 Quality value parser (and without memory allocation)
|
||||||
|
// See https://tools.ietf.org/html/rfc7231#section-5.3.1
|
||||||
|
// Check is made to verify if the value is between 0 and 1 (and it returns False if the check fails).
|
||||||
|
internal static bool TryParseQualityDouble(string input, int startIndex, out double quality, out int length)
|
||||||
|
{
|
||||||
|
quality = 0;
|
||||||
|
length = 0;
|
||||||
|
|
||||||
|
var inputLength = input.Length;
|
||||||
|
var current = startIndex;
|
||||||
|
var limit = startIndex + _qualityValueMaxCharCount;
|
||||||
|
|
||||||
|
var intPart = 0;
|
||||||
|
var decPart = 0;
|
||||||
|
var decPow = 1;
|
||||||
|
|
||||||
|
if (current >= inputLength)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ch = input[current];
|
||||||
|
|
||||||
|
if (ch >= '0' && ch <= '1') // Only values between 0 and 1 are accepted, according to RFC
|
||||||
|
{
|
||||||
|
intPart = ch - '0';
|
||||||
|
current++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The RFC doesn't allow decimal values starting with dot. I.e. value ".123" is invalid. It must be in the
|
||||||
|
// form "0.123".
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current < inputLength)
|
||||||
|
{
|
||||||
|
ch = input[current];
|
||||||
|
|
||||||
|
if (ch >= '0' && ch <= '9')
|
||||||
|
{
|
||||||
|
// The RFC accepts only one digit before the dot
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '.')
|
||||||
|
{
|
||||||
|
current++;
|
||||||
|
|
||||||
|
while (current < inputLength)
|
||||||
|
{
|
||||||
|
ch = input[current];
|
||||||
|
if (ch >= '0' && ch <= '9')
|
||||||
|
{
|
||||||
|
if (current >= limit)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
decPart = decPart * 10 + ch - '0';
|
||||||
|
decPow *= 10;
|
||||||
|
current++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decPart != 0)
|
||||||
|
{
|
||||||
|
quality = intPart + decPart / (double)decPow;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
quality = intPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quality < 0 || quality > 1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
length = current - startIndex;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts the non-negative 64-bit numeric value to its equivalent string representation.
|
/// Converts the non-negative 64-bit numeric value to its equivalent string representation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -61,13 +61,14 @@ namespace Microsoft.Net.Http.Headers
|
||||||
// If a parser returns an empty list, it means there was no value, but that's valid (e.g. "Accept: "). The caller
|
// If a parser returns an empty list, it means there was no value, but that's valid (e.g. "Accept: "). The caller
|
||||||
// can ignore the value.
|
// can ignore the value.
|
||||||
parsedValues = null;
|
parsedValues = null;
|
||||||
var results = new List<T>();
|
List<T> results = null;
|
||||||
if (values == null)
|
if (values == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
foreach (var value in values)
|
for (var i = 0; i < values.Count; i++)
|
||||||
{
|
{
|
||||||
|
var value = values[i];
|
||||||
int index = 0;
|
int index = 0;
|
||||||
|
|
||||||
while (!string.IsNullOrEmpty(value) && index < value.Length)
|
while (!string.IsNullOrEmpty(value) && index < value.Length)
|
||||||
|
|
@ -78,6 +79,10 @@ namespace Microsoft.Net.Http.Headers
|
||||||
// The entry may not contain an actual value, like " , "
|
// The entry may not contain an actual value, like " , "
|
||||||
if (output != null)
|
if (output != null)
|
||||||
{
|
{
|
||||||
|
if (results == null)
|
||||||
|
{
|
||||||
|
results = new List<T>(); // Allocate it only when used
|
||||||
|
}
|
||||||
results.Add(output);
|
results.Add(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +97,7 @@ namespace Microsoft.Net.Http.Headers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (results.Count > 0)
|
if (results != null)
|
||||||
{
|
{
|
||||||
parsedValues = results;
|
parsedValues = results;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -204,21 +204,7 @@ namespace Microsoft.Net.Http.Headers
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int qualityLength = HttpRuleParser.GetNumberLength(input, current, true);
|
if (!HeaderUtilities.TryParseQualityDouble(input, current, out var quality, out var qualityLength))
|
||||||
|
|
||||||
if (qualityLength == 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
double quality;
|
|
||||||
if (!double.TryParse(input.Substring(current, qualityLength), NumberStyles.AllowDecimalPoint,
|
|
||||||
NumberFormatInfo.InvariantInfo, out quality))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((quality < 0) || (quality > 1))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,8 @@ namespace Microsoft.Net.Http.Headers
|
||||||
CheckValidParse(" t", new StringWithQualityHeaderValue("t"));
|
CheckValidParse(" t", new StringWithQualityHeaderValue("t"));
|
||||||
CheckValidParse("t;q=0.", new StringWithQualityHeaderValue("t", 0));
|
CheckValidParse("t;q=0.", new StringWithQualityHeaderValue("t", 0));
|
||||||
CheckValidParse("t;q=1.", new StringWithQualityHeaderValue("t", 1));
|
CheckValidParse("t;q=1.", new StringWithQualityHeaderValue("t", 1));
|
||||||
|
CheckValidParse("t;q=1.000", new StringWithQualityHeaderValue("t", 1));
|
||||||
|
CheckValidParse("t;q=0.12345678", new StringWithQualityHeaderValue("t", 0.12345678));
|
||||||
CheckValidParse("t ; q = 0", new StringWithQualityHeaderValue("t", 0));
|
CheckValidParse("t ; q = 0", new StringWithQualityHeaderValue("t", 0));
|
||||||
CheckValidParse("iso-8859-5", new StringWithQualityHeaderValue("iso-8859-5"));
|
CheckValidParse("iso-8859-5", new StringWithQualityHeaderValue("iso-8859-5"));
|
||||||
CheckValidParse("unicode-1-1; q=0.8", new StringWithQualityHeaderValue("unicode-1-1", 0.8));
|
CheckValidParse("unicode-1-1; q=0.8", new StringWithQualityHeaderValue("unicode-1-1", 0.8));
|
||||||
|
|
@ -154,6 +156,11 @@ namespace Microsoft.Net.Http.Headers
|
||||||
[InlineData("t;q=a")]
|
[InlineData("t;q=a")]
|
||||||
[InlineData("t;qa")]
|
[InlineData("t;qa")]
|
||||||
[InlineData("t;q1")]
|
[InlineData("t;q1")]
|
||||||
|
[InlineData("integer_part_too_long;q=01")]
|
||||||
|
[InlineData("integer_part_too_long;q=01.0")]
|
||||||
|
[InlineData("decimal_part_too_long;q=0.123456789")]
|
||||||
|
[InlineData("decimal_part_too_long;q=0.123456789 ")]
|
||||||
|
[InlineData("no_integer_part;q=.1")]
|
||||||
public void Parse_SetOfInvalidValueStrings_Throws(string input)
|
public void Parse_SetOfInvalidValueStrings_Throws(string input)
|
||||||
{
|
{
|
||||||
Assert.Throws<FormatException>(() => StringWithQualityHeaderValue.Parse(input));
|
Assert.Throws<FormatException>(() => StringWithQualityHeaderValue.Parse(input));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue