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

Update C# code for #29448 #56086

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
Next Next commit
Refactor UriHelper.cs to use a static lambda and UriComponents struct
This commit includes a significant refactor of the `UriHelper.cs` file. Unused namespaces `System.Buffers` and `System.Runtime.CompilerServices` have been removed. The `InitializeAbsoluteUriStringSpanAction` delegate and `InitializeAbsoluteUriString` method, as well as the `CopyTextToBuffer` method, have been removed.

A new struct `UriComponents` has been introduced to encapsulate the components of a URI: `Scheme`, `Host`, `PathBase`, `Path`, `Query`, and `Fragment`. The `string.Create` method call has been updated to use a new lambda function that leverages the `UriComponents` struct to copy each component of the URI into the buffer, adjusting the buffer slice as needed. This function also handles the case where both `PathBase` and `Path` components have a slash, removing the trailing slash from `PathBase` to avoid duplication.

Finally, the `BuildAbsolute` method has been updated to use the new `UriComponents` struct and the updated `string.Create` call.
  • Loading branch information
paulomorgado committed Nov 15, 2024
commit 65b683427f3ee6f1d77f3c88d58abc7206389385
89 changes: 49 additions & 40 deletions src/Http/Http.Extensions/src/UriHelper.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;

namespace Microsoft.AspNetCore.Http.Extensions;
@@ -17,7 +15,6 @@ public static class UriHelper
private const char Hash = '#';
private const char QuestionMark = '?';
private static readonly string SchemeDelimiter = Uri.SchemeDelimiter;
private static readonly SpanAction<char, (string scheme, string host, string pathBase, string path, string query, string fragment)> InitializeAbsoluteUriStringSpanAction = new(InitializeAbsoluteUriString);

/// <summary>
/// Combines the given URI components into a string that is properly encoded for use in HTTP headers.
@@ -90,7 +87,41 @@ public static string BuildAbsolute(
length--;
}

return string.Create(length, (scheme, hostText, pathBaseText, pathText, queryText, fragmentText), InitializeAbsoluteUriStringSpanAction);
return string.Create(
length,
new UriComponents(scheme, hostText, pathBaseText, pathText, queryText, fragmentText),
static (buffer, uriComponents) =>
{
uriComponents.Scheme.AsSpan().CopyTo(buffer);
buffer = buffer.Slice(uriComponents.Scheme.Length);

Uri.SchemeDelimiter.CopyTo(buffer);
buffer = buffer.Slice(Uri.SchemeDelimiter.Length);

uriComponents.Host.AsSpan().CopyTo(buffer);
buffer = buffer.Slice(uriComponents.Host.Length);

var pathBaseSpan = uriComponents.PathBase.AsSpan();

if (uriComponents.Path.Length > 0 && pathBaseSpan.Length > 0 && pathBaseSpan[^1] == '/')
{
// If the path string has a trailing slash and the other string has a leading slash, we need
// to trim one of them.
// Trim the last slash from pathBase. The total length was decremented before the call to string.Create.
pathBaseSpan = pathBaseSpan[..^1];
}

pathBaseSpan.CopyTo(buffer);
buffer = buffer.Slice(pathBaseSpan.Length);

uriComponents.Path.CopyTo(buffer);
buffer = buffer.Slice(uriComponents.Path.Length);

uriComponents.Query.CopyTo(buffer);
buffer = buffer.Slice(uriComponents.Query.Length);

uriComponents.Fragment.CopyTo(buffer);
});
}

/// <summary>
@@ -224,45 +255,23 @@ public static string GetDisplayUrl(this HttpRequest request)
.ToString();
}

/// <summary>
/// Copies the specified <paramref name="text"/> to the specified <paramref name="buffer"/> starting at the specified <paramref name="index"/>.
/// </summary>
/// <param name="buffer">The buffer to copy text to.</param>
/// <param name="index">The buffer start index.</param>
/// <param name="text">The text to copy.</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int CopyTextToBuffer(Span<char> buffer, int index, ReadOnlySpan<char> text)
{
text.CopyTo(buffer.Slice(index, text.Length));
return index + text.Length;
}

/// <summary>
/// Initializes the URI <see cref="string"/> for <see cref="BuildAbsolute(string, HostString, PathString, PathString, QueryString, FragmentString)"/>.
/// </summary>
/// <param name="buffer">The URI <see cref="string"/>'s <see cref="char"/> buffer.</param>
/// <param name="uriParts">The URI parts.</param>
private static void InitializeAbsoluteUriString(Span<char> buffer, (string scheme, string host, string pathBase, string path, string query, string fragment) uriParts)
private readonly struct UriComponents
{
var index = 0;

var pathBaseSpan = uriParts.pathBase.AsSpan();
public readonly string Scheme;
public readonly string Host;
public readonly string PathBase;
public readonly string Path;
public readonly string Query;
public readonly string Fragment;

if (uriParts.path.Length > 0 && pathBaseSpan.Length > 0 && pathBaseSpan[^1] == '/')
public UriComponents(string scheme, string host, string pathBase, string path, string query, string fragment)
{
// If the path string has a trailing slash and the other string has a leading slash, we need
// to trim one of them.
// Trim the last slahs from pathBase. The total length was decremented before the call to string.Create.
pathBaseSpan = pathBaseSpan[..^1];
Scheme = scheme;
Host = host;
PathBase = pathBase;
Path = path;
Query = query;
Fragment = fragment;
}

index = CopyTextToBuffer(buffer, index, uriParts.scheme.AsSpan());
index = CopyTextToBuffer(buffer, index, Uri.SchemeDelimiter.AsSpan());
index = CopyTextToBuffer(buffer, index, uriParts.host.AsSpan());
index = CopyTextToBuffer(buffer, index, pathBaseSpan);
index = CopyTextToBuffer(buffer, index, uriParts.path.AsSpan());
index = CopyTextToBuffer(buffer, index, uriParts.query.AsSpan());
_ = CopyTextToBuffer(buffer, index, uriParts.fragment.AsSpan());
}
}