Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[release/8.0] Improved DownstreamDependencyMetadataManager to handle trailing slash… #4426

Merged
merged 2 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -55,7 +55,4 @@
<InternalsVisibleTo Include="Microsoft.AspNetCore.Telemetry.Middleware" />
</ItemGroup>

<ItemGroup>
<Folder Include="Tracing.Sampling\Internal\" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,27 @@ private static void AddRouteToTrie(RequestMetadata routeMetadata, Dictionary<str
var trieCurrent = routeMetadataTrieRoot;
trieCurrent.Parent = trieCurrent;

ReadOnlySpan<char> requestRouteAsSpan;

if (routeMetadata.RequestRoute[0] != '/')
{
routeMetadata.RequestRoute = $"/{routeMetadata.RequestRoute}";
requestRouteAsSpan = $"/{routeMetadata.RequestRoute}".AsSpan();
}
else if (routeMetadata.RequestRoute.StartsWith("//", StringComparison.OrdinalIgnoreCase))
{
requestRouteAsSpan = routeMetadata.RequestRoute.AsSpan(1);
}
else
{
requestRouteAsSpan = routeMetadata.RequestRoute.AsSpan();
}

if (requestRouteAsSpan[requestRouteAsSpan.Length - 1] == '/')
{
requestRouteAsSpan = requestRouteAsSpan.Slice(0, requestRouteAsSpan.Length - 1);
}

var route = _routeRegex.Replace(routeMetadata.RequestRoute, "*");
var route = _routeRegex.Replace(requestRouteAsSpan.ToString(), "*");
route = route.ToUpperInvariant();
for (int i = 0; i < route.Length; i++)
{
Expand Down Expand Up @@ -335,12 +350,27 @@ private void AddHostnameToTrie(string hostNameSuffix, string dependencyName)
return hostMetadata.RequestMetadata;
}

ReadOnlySpan<char> requestRouteAsSpan;
if (requestPath[requestPath.Length - 1] == '/')
{
requestRouteAsSpan = requestPath.AsSpan(0, requestPath.Length - 1);
}
else
{
requestRouteAsSpan = requestPath.AsSpan();
}

if (requestPath.StartsWith("//", StringComparison.OrdinalIgnoreCase))
{
requestRouteAsSpan = requestRouteAsSpan.Slice(1);
}

var trieCurrent = routeMetadataTrieRoot.Nodes[0];
var lastStartNode = trieCurrent;
var requestPathEndIndex = requestPath.Length;
var requestPathEndIndex = requestRouteAsSpan.Length;
for (int i = 0; i < requestPathEndIndex; i++)
{
char ch = _toUpper[requestPath[i]];
char ch = _toUpper[requestRouteAsSpan[i]];
var childNode = GetChildNode(ch, trieCurrent, routeMetadataTrieRoot);
if (childNode == null)
{
Expand All @@ -359,11 +389,12 @@ private void AddHostnameToTrie(string hostNameSuffix, string dependencyName)
break;
}

var nextDelimiterIndex = requestPath.IndexOf(trieCurrent.Delimiter, i, requestPathEndIndex - i);
// we add i to the index, because the index returned from ReadOnlySpan<char> is the index in the new slice, not in the original slice.
var nextDelimiterIndex = requestRouteAsSpan.Slice(i, requestPathEndIndex - i).IndexOf(trieCurrent.Delimiter) + i;

// if we reached end of the request path or end of trie, break
var delimChildNode = GetChildNode(trieCurrent.Delimiter, trieCurrent, routeMetadataTrieRoot);
if (nextDelimiterIndex == -1 || delimChildNode == null)
if (nextDelimiterIndex == i - 1 || delimChildNode == null)
{
break;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using Microsoft.Extensions.Http.Telemetry;

namespace Microsoft.Extensions.Telemetry.Telemetry;

internal sealed class BackslashDownstreamDependencyMetadata : IDownstreamDependencyMetadata
{
private static readonly ISet<string> _uniqueHostNameSuffixes = new HashSet<string>
{
"anotherservice.net",
};

private static readonly ISet<RequestMetadata> _requestMetadataSet = new HashSet<RequestMetadata>
{
new ("DELETE", "/singlebackslash", "StartingSingleBackslash"),
new ("POST", "//doublebackslash", "StartingDoublebackslash"),
new ("PUT", "/singlethensingle/", "StartingSingleBackslashEndingSingleBackslash"),
new ("GET", "//doublethensingle/", "StartingDoublebackslashEndingSingleBackslash"),
};

public string DependencyName => "BackslashService";

public ISet<string> UniqueHostNameSuffixes => _uniqueHostNameSuffixes;

public ISet<RequestMetadata> RequestMetadata => _requestMetadataSet;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Net.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Telemetry.Internal;
using Xunit;

namespace Microsoft.Extensions.Telemetry.Telemetry;

public class DownstreamDependencyMetadataManagerTests : IDisposable
{
private readonly IDownstreamDependencyMetadataManager _depMetadataManager;
private readonly ServiceProvider _sp;

public DownstreamDependencyMetadataManagerTests()
{
_sp = new ServiceCollection()
.AddDownstreamDependencyMetadata(new BackslashDownstreamDependencyMetadata())
.BuildServiceProvider();
_depMetadataManager = _sp.GetRequiredService<IDownstreamDependencyMetadataManager>();
}

[Theory]
[InlineData("DELETE", "https://anotherservice.net/singlebackslash", "StartingSingleBackslash", "/singlebackslash")]
[InlineData("POST", "https://anotherservice.net/doublebackslash", "StartingDoublebackslash", "//doublebackslash")]
[InlineData("PUT", "https://anotherservice.net/singlethensingle", "StartingSingleBackslashEndingSingleBackslash", "/singlethensingle/")]
[InlineData("GET", "https://anotherservice.net/doublethensingle", "StartingDoublebackslashEndingSingleBackslash", "//doublethensingle/")]

[InlineData("DELETE", "https://anotherservice.net//singlebackslash", "StartingSingleBackslash", "/singlebackslash")]
[InlineData("POST", "https://anotherservice.net//doublebackslash", "StartingDoublebackslash", "//doublebackslash")]
[InlineData("PUT", "https://anotherservice.net//singlethensingle", "StartingSingleBackslashEndingSingleBackslash", "/singlethensingle/")]
[InlineData("GET", "https://anotherservice.net//doublethensingle", "StartingDoublebackslashEndingSingleBackslash", "//doublethensingle/")]

[InlineData("DELETE", "https://anotherservice.net/singlebackslash/", "StartingSingleBackslash", "/singlebackslash")]
[InlineData("POST", "https://anotherservice.net/doublebackslash/", "StartingDoublebackslash", "//doublebackslash")]
[InlineData("PUT", "https://anotherservice.net/singlethensingle/", "StartingSingleBackslashEndingSingleBackslash", "/singlethensingle/")]
[InlineData("GET", "https://anotherservice.net/doublethensingle/", "StartingDoublebackslashEndingSingleBackslash", "//doublethensingle/")]

[InlineData("DELETE", "https://anotherservice.net//singlebackslash/", "StartingSingleBackslash", "/singlebackslash")]
[InlineData("POST", "https://anotherservice.net//doublebackslash/", "StartingDoublebackslash", "//doublebackslash")]
[InlineData("PUT", "https://anotherservice.net//singlethensingle/", "StartingSingleBackslashEndingSingleBackslash", "/singlethensingle/")]
[InlineData("GET", "https://anotherservice.net//doublethensingle/", "StartingDoublebackslashEndingSingleBackslash", "//doublethensingle/")]
public void GetRequestMetadata_RoutesRegisteredWithBackslashes_ShouldReturnHostMetadata(string httpMethod, string urlString, string expectedRequestName, string expectedRequestRoute)
{
using var requestMessage = new HttpRequestMessage
{
Method = new HttpMethod(method: httpMethod),
RequestUri = new Uri(uriString: urlString)
};

var requestMetadata = _depMetadataManager.GetRequestMetadata(requestMessage);
Assert.NotNull(requestMetadata);
Assert.Equal(new BackslashDownstreamDependencyMetadata().DependencyName, requestMetadata.DependencyName);
Assert.Equal(expectedRequestName, requestMetadata.RequestName);
Assert.Equal(expectedRequestRoute, requestMetadata.RequestRoute);
Assert.Equal(httpMethod, requestMetadata.MethodType);
}

protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_sp.Dispose();
}
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}