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
|
||||
{
|
||||
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";
|
||||
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
|
||||
// separator is considered invalid (even if the current culture would allow it).
|
||||
double qualityValue;
|
||||
if (double.TryParse(qualityParameter.Value, NumberStyles.AllowDecimalPoint,
|
||||
NumberFormatInfo.InvariantInfo, out qualityValue))
|
||||
if (TryParseQualityDouble(qualityParameter.Value, 0, out var qualityValue, out var length))
|
||||
|
||||
{
|
||||
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>
|
||||
/// Converts the non-negative 64-bit numeric value to its equivalent string representation.
|
||||
/// </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
|
||||
// can ignore the value.
|
||||
parsedValues = null;
|
||||
var results = new List<T>();
|
||||
List<T> results = null;
|
||||
if (values == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (var value in values)
|
||||
for (var i = 0; i < values.Count; i++)
|
||||
{
|
||||
var value = values[i];
|
||||
int index = 0;
|
||||
|
||||
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 " , "
|
||||
if (output != null)
|
||||
{
|
||||
if (results == null)
|
||||
{
|
||||
results = new List<T>(); // Allocate it only when used
|
||||
}
|
||||
results.Add(output);
|
||||
}
|
||||
}
|
||||
|
|
@ -92,7 +97,7 @@ namespace Microsoft.Net.Http.Headers
|
|||
}
|
||||
}
|
||||
}
|
||||
if (results.Count > 0)
|
||||
if (results != null)
|
||||
{
|
||||
parsedValues = results;
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -204,21 +204,7 @@ namespace Microsoft.Net.Http.Headers
|
|||
return false;
|
||||
}
|
||||
|
||||
int qualityLength = HttpRuleParser.GetNumberLength(input, current, true);
|
||||
|
||||
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))
|
||||
if (!HeaderUtilities.TryParseQualityDouble(input, current, out var quality, out var qualityLength))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,6 +121,8 @@ namespace Microsoft.Net.Http.Headers
|
|||
CheckValidParse(" t", new StringWithQualityHeaderValue("t"));
|
||||
CheckValidParse("t;q=0.", new StringWithQualityHeaderValue("t", 0));
|
||||
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("iso-8859-5", new StringWithQualityHeaderValue("iso-8859-5"));
|
||||
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;qa")]
|
||||
[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)
|
||||
{
|
||||
Assert.Throws<FormatException>(() => StringWithQualityHeaderValue.Parse(input));
|
||||
|
|
|
|||
Loading…
Reference in New Issue