Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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 @@ -33,29 +33,47 @@ public static THeaders GetHeaders<THeaders>(this OtlpExporterOptions options, Ac
optionHeaders = Uri.UnescapeDataString(optionHeaders);
ReadOnlySpan<char> headersSpan = optionHeaders.AsSpan();

var nextEqualIndex = headersSpan.IndexOf('=');

if (nextEqualIndex == -1)
{
throw new ArgumentException("Headers provided in an invalid format.");
}

while (!headersSpan.IsEmpty)
{
int commaIndex = headersSpan.IndexOf(',');
ReadOnlySpan<char> pair;
if (commaIndex == -1)
var key = headersSpan.Slice(0, nextEqualIndex).Trim().ToString();
Copy link
Contributor

@matt-hensley matt-hensley Sep 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

likely able to avoid the additional string allocations and only use spans for the new parsing logic

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you mean? I'm already using Span for the parsing, but there's no way around allocating a string for the key and value at some point.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initial read looked like there were multiple ToString calls. looks reasonable to push those calls as late as possible

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a concrete idea of how to do so? I'm not even sure if performance improvements in this part of the code matter that much. This isn't even on any hot path right?


headersSpan = headersSpan.Slice(nextEqualIndex + 1);

nextEqualIndex = headersSpan.IndexOf('=');

string value;
if (nextEqualIndex == -1)
{
pair = headersSpan;
// Everything until the end of the string can be considered the value.
value = headersSpan.Trim().ToString();
headersSpan = [];
}
else
{
pair = headersSpan.Slice(0, commaIndex);
headersSpan = headersSpan.Slice(commaIndex + 1);
}
// If we have another = we need to backtrack from it
// and try to find the last comma and consider that as the delimiter.
var potentialValue = headersSpan.Slice(0, nextEqualIndex);
var lastComma = potentialValue.LastIndexOf(',');

int equalIndex = pair.IndexOf('=');
if (equalIndex == -1)
{
throw new ArgumentException("Headers provided in an invalid format.");
if (lastComma == -1)
{
throw new ArgumentException("Headers provided in an invalid format.");
}

potentialValue = potentialValue.Slice(0, lastComma);

value = potentialValue.Trim().ToString();
headersSpan = headersSpan.Slice(lastComma + 1);
nextEqualIndex -= potentialValue.Length + 1;
}

var key = pair.Slice(0, equalIndex).Trim().ToString();
var value = pair.Slice(equalIndex + 1).Trim().ToString();
addHeader(headers, key, value);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,16 @@ public void GetHeaders_InvalidOptionHeaders_ThrowsArgumentException(string input
[InlineData("key1=value1", "key1=value1")]
[InlineData("key1=value1,key2=value2", "key1=value1,key2=value2")]
[InlineData("key1=value1,key2=value2,key3=value3", "key1=value1,key2=value2,key3=value3")]
[InlineData("key1=value1,value2", "key1=value1,value2")]
[InlineData("key1=value1,value2,key2=value3", "key1=value1,value2,key2=value3")]
[InlineData(" key1 = value1 , key2=value2 ", "key1=value1,key2=value2")]
[InlineData("key1= value with spaces ,key2=another value", "key1=value with spaces,key2=another value")]
[InlineData("=value1", "=value1")]
[InlineData("key1=", "key1=")]
[InlineData("key1=value1%2Ckey2=value2", "key1=value1,key2=value2")]
[InlineData("key1=value1%2Ckey2=value2%2Ckey3=value3", "key1=value1,key2=value2,key3=value3")]
[InlineData("key1=value1%2Cvalue2", "key1=value1,value2")]
[InlineData("key1=value1%2Cvalue2%2Ckey2=value3", "key1=value1,value2,key2=value3")]
public void GetHeaders_ValidAndUrlEncodedHeaders_ReturnsCorrectHeaders(string inputOptionHeaders, string expectedNormalizedOptional)
{
VerifyHeaders(inputOptionHeaders, expectedNormalizedOptional);
Expand Down Expand Up @@ -175,27 +179,16 @@ private static void VerifyHeaders(string inputOptionHeaders, string expectedNorm
}

var headers = options.GetHeaders<Dictionary<string, string>>((d, k, v) => d.Add(k, v));
var expectedOptional = new Dictionary<string, string>();

if (!string.IsNullOrEmpty(expectedNormalizedOptional))
{
foreach (var segment in expectedNormalizedOptional.Split([','], StringSplitOptions.RemoveEmptyEntries))
{
var parts = segment.Split(['='], 2);
expectedOptional.Add(parts[0].Trim(), parts[1].Trim());
}
}
var actual = string.Join(",", headers.Select(h => $"{h.Key}={h.Value}"));

Assert.Equal(OtlpExporterOptions.StandardHeaders.Length + expectedOptional.Count, headers.Count);

foreach (var kvp in expectedOptional)
var expected = expectedNormalizedOptional;
if (expected.Length > 0)
{
Assert.Contains(headers, h => h.Key == kvp.Key && h.Value == kvp.Value);
expected += ',';
}
expected += string.Join(",", OtlpExporterOptions.StandardHeaders.Select(h => $"{h.Key}={h.Value}"));

foreach (var std in OtlpExporterOptions.StandardHeaders)
{
Assert.Contains(headers, h => h.Key == std.Key && h.Value == std.Value);
}
Assert.Equal(expected, actual);
}
}