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

Cache more info on HttpMethod #100177

Merged
merged 2 commits into from
Mar 24, 2024
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
2 changes: 1 addition & 1 deletion src/libraries/System.Net.Http/src/System.Net.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@
<!-- SocketsHttpHandler implementation -->
<ItemGroup Condition="'$(TargetPlatformIdentifier)' != '' and '$(TargetPlatformIdentifier)' != 'browser'">
<Compile Include="System\Net\Http\HttpHandlerDefaults.cs" />
<Compile Include="System\Net\Http\HttpMethod.Http3.cs" />
<Compile Include="System\Net\Http\HttpMethod.SocketsHttpHandler.cs" />
<Compile Include="System\Net\Http\Headers\AltSvcHeaderParser.cs" />
<Compile Include="System\Net\Http\Headers\AltSvcHeaderValue.cs" />
<Compile Include="System\Net\Http\Headers\KnownHeader.Http2And3.cs" />
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Net.Http.HPack;
using System.Net.Http.QPack;
using System.Text;

namespace System.Net.Http
{
public partial class HttpMethod
{
private byte[]? _http1EncodedBytes;
private byte[]? _http2EncodedBytes;
private byte[]? _http3EncodedBytes;
private int _http3Index;

internal bool MustHaveRequestBody { get; private set; }
internal bool IsConnect { get; private set; }
internal bool IsHead { get; private set; }

partial void Initialize(string method)
{
Initialize(GetKnownMethod(method)?._http3Index ?? 0);
}

partial void Initialize(int http3Index)
{
_http3Index = http3Index;

if (http3Index == H3StaticTable.MethodConnect)
{
IsConnect = true;
}
else if (http3Index == H3StaticTable.MethodHead)
{
IsHead = true;
}
else
{
MustHaveRequestBody = http3Index is not (H3StaticTable.MethodGet or H3StaticTable.MethodOptions or H3StaticTable.MethodDelete);
}
}

internal byte[] Http1EncodedBytes => _http1EncodedBytes ?? CreateHttp1EncodedBytes();
internal byte[] Http2EncodedBytes => _http2EncodedBytes ?? CreateHttp2EncodedBytes();
internal byte[] Http3EncodedBytes => _http3EncodedBytes ?? CreateHttp3EncodedBytes();

private byte[] CreateHttp1EncodedBytes()
{
HttpMethod? knownMethod = GetKnownMethod(Method);
byte[]? bytes = knownMethod?._http1EncodedBytes;

if (bytes is null)
{
Debug.Assert(Ascii.IsValid(Method));

string method = knownMethod?.Method ?? Method;
bytes = new byte[method.Length + 1];
Ascii.FromUtf16(method, bytes, out _);
bytes[^1] = (byte)' ';

if (knownMethod is not null)
{
knownMethod._http1EncodedBytes = bytes;
}
}

_http1EncodedBytes = bytes;
return bytes;
}

private byte[] CreateHttp2EncodedBytes()
{
HttpMethod? knownMethod = GetKnownMethod(Method);
byte[]? bytes = knownMethod?._http2EncodedBytes;

if (bytes is null)
{
bytes = _http3Index switch
{
H3StaticTable.MethodGet => [0x80 | H2StaticTable.MethodGet],
H3StaticTable.MethodPost => [0x80 | H2StaticTable.MethodPost],
_ => HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingToAllocatedArray(H2StaticTable.MethodGet, knownMethod?.Method ?? Method)
};

if (knownMethod is not null)
{
knownMethod._http2EncodedBytes = bytes;
}
}

_http2EncodedBytes = bytes;
return bytes;
}

private byte[] CreateHttp3EncodedBytes()
{
HttpMethod? knownMethod = GetKnownMethod(Method);
byte[]? bytes = knownMethod?._http3EncodedBytes;

if (bytes is null)
{
bytes = _http3Index > 0
? QPackEncoder.EncodeStaticIndexedHeaderFieldToArray(_http3Index)
: QPackEncoder.EncodeLiteralHeaderFieldWithStaticNameReferenceToArray(H3StaticTable.MethodGet, knownMethod?.Method ?? Method);

if (knownMethod is not null)
{
knownMethod._http3EncodedBytes = bytes;
}
}

_http3EncodedBytes = bytes;
return bytes;
}
}
}
173 changes: 37 additions & 136 deletions src/libraries/System.Net.Http/src/System/Net/Http/HttpMethod.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net.Http.QPack;

Expand All @@ -10,71 +9,22 @@ namespace System.Net.Http
public partial class HttpMethod : IEquatable<HttpMethod>
{
private readonly string _method;
private readonly int? _http3Index;

private int _hashcode;

private static readonly HttpMethod s_getMethod = new HttpMethod("GET", http3StaticTableIndex: H3StaticTable.MethodGet);
private static readonly HttpMethod s_putMethod = new HttpMethod("PUT", http3StaticTableIndex: H3StaticTable.MethodPut);
private static readonly HttpMethod s_postMethod = new HttpMethod("POST", http3StaticTableIndex: H3StaticTable.MethodPost);
private static readonly HttpMethod s_deleteMethod = new HttpMethod("DELETE", http3StaticTableIndex: H3StaticTable.MethodDelete);
private static readonly HttpMethod s_headMethod = new HttpMethod("HEAD", http3StaticTableIndex: H3StaticTable.MethodHead);
private static readonly HttpMethod s_optionsMethod = new HttpMethod("OPTIONS", http3StaticTableIndex: H3StaticTable.MethodOptions);
private static readonly HttpMethod s_traceMethod = new HttpMethod("TRACE", -1);
private static readonly HttpMethod s_patchMethod = new HttpMethod("PATCH", -1);
private static readonly HttpMethod s_connectMethod = new HttpMethod("CONNECT", http3StaticTableIndex: H3StaticTable.MethodConnect);

public static HttpMethod Get
{
get { return s_getMethod; }
}

public static HttpMethod Put
{
get { return s_putMethod; }
}

public static HttpMethod Post
{
get { return s_postMethod; }
}

public static HttpMethod Delete
{
get { return s_deleteMethod; }
}

public static HttpMethod Head
{
get { return s_headMethod; }
}

public static HttpMethod Options
{
get { return s_optionsMethod; }
}

public static HttpMethod Trace
{
get { return s_traceMethod; }
}

public static HttpMethod Patch
{
get { return s_patchMethod; }
}
public static HttpMethod Get { get; } = new("GET", H3StaticTable.MethodGet);
public static HttpMethod Put { get; } = new("PUT", H3StaticTable.MethodPut);
public static HttpMethod Post { get; } = new("POST", H3StaticTable.MethodPost);
public static HttpMethod Delete { get; } = new("DELETE", H3StaticTable.MethodDelete);
public static HttpMethod Head { get; } = new("HEAD", H3StaticTable.MethodHead);
public static HttpMethod Options { get; } = new("OPTIONS", H3StaticTable.MethodOptions);
public static HttpMethod Trace { get; } = new("TRACE", http3StaticTableIndex: -1);
public static HttpMethod Patch { get; } = new("PATCH", http3StaticTableIndex: -1);

/// <summary>Gets the HTTP CONNECT protocol method.</summary>
/// <value>The HTTP CONNECT method.</value>
public static HttpMethod Connect
{
get { return s_connectMethod; }
}
public static HttpMethod Connect { get; } = new("CONNECT", H3StaticTable.MethodConnect);

public string Method
{
get { return _method; }
}
public string Method => _method;

public HttpMethod(string method)
{
Expand All @@ -85,39 +35,26 @@ public HttpMethod(string method)
}

_method = method;
Initialize(method);
}

private HttpMethod(string method, int http3StaticTableIndex)
{
_method = method;
_http3Index = http3StaticTableIndex;
Initialize(http3StaticTableIndex);
}

#region IEquatable<HttpMethod> Members

public bool Equals([NotNullWhen(true)] HttpMethod? other)
{
if (other is null)
{
return false;
}

if (object.ReferenceEquals(_method, other._method))
{
// Strings are static, so there is a good chance that two equal methods use the same reference
// (unless they differ in case).
return true;
}

return string.Equals(_method, other._method, StringComparison.OrdinalIgnoreCase);
}
// SocketsHttpHandler-specific implementation has extra init logic.
partial void Initialize(int http3Index);
partial void Initialize(string method);

#endregion
public bool Equals([NotNullWhen(true)] HttpMethod? other) =>
other is not null &&
string.Equals(_method, other._method, StringComparison.OrdinalIgnoreCase);

public override bool Equals([NotNullWhen(true)] object? obj)
{
return Equals(obj as HttpMethod);
}
public override bool Equals([NotNullWhen(true)] object? obj) =>
obj is HttpMethod method &&
Equals(method);

public override int GetHashCode()
{
Expand All @@ -129,22 +66,15 @@ public override int GetHashCode()
return _hashcode;
}

public override string ToString()
{
return _method;
}
public override string ToString() => _method;

public static bool operator ==(HttpMethod? left, HttpMethod? right)
{
return left is null || right is null ?
ReferenceEquals(left, right) :
left.Equals(right);
}
public static bool operator ==(HttpMethod? left, HttpMethod? right) =>
left is null || right is null
? ReferenceEquals(left, right)
: left.Equals(right);

public static bool operator !=(HttpMethod? left, HttpMethod? right)
{
return !(left == right);
}
public static bool operator !=(HttpMethod? left, HttpMethod? right) =>
!(left == right);

/// <summary>Parses the provided <paramref name="method"/> into an <see cref="HttpMethod"/> instance.</summary>
/// <param name="method">The method to parse.</param>
Expand All @@ -159,41 +89,24 @@ public static HttpMethod Parse(ReadOnlySpan<char> method) =>
GetKnownMethod(method) ??
new HttpMethod(method.ToString());

/// <summary>
/// Returns a singleton method instance with a capitalized method name for the supplied method
/// if it's known; otherwise, returns the original.
/// </summary>
internal static HttpMethod Normalize(HttpMethod method)
{
Debug.Assert(method != null);
Debug.Assert(!string.IsNullOrEmpty(method._method));

// _http3Index is only set for the singleton instances, so if it's not null,
// we can avoid the lookup. Otherwise, look up the method instance and return the
// normalized instance if it's found.
return method._http3Index is null && GetKnownMethod(method._method) is HttpMethod match ?
match :
method;
}

internal static HttpMethod? GetKnownMethod(ReadOnlySpan<char> method)
{
if (method.Length >= 3) // 3 == smallest known method
{
HttpMethod? match = (method[0] | 0x20) switch
{
'c' => s_connectMethod,
'd' => s_deleteMethod,
'g' => s_getMethod,
'h' => s_headMethod,
'o' => s_optionsMethod,
'c' => Connect,
'd' => Delete,
'g' => Get,
'h' => Head,
'o' => Options,
'p' => method.Length switch
{
3 => s_putMethod,
4 => s_postMethod,
_ => s_patchMethod,
3 => Put,
4 => Post,
_ => Patch,
},
't' => s_traceMethod,
't' => Trace,
_ => null,
};

Expand All @@ -206,17 +119,5 @@ internal static HttpMethod Normalize(HttpMethod method)

return null;
}

internal bool MustHaveRequestBody
{
get
{
// Normalize before calling this
Debug.Assert(ReferenceEquals(this, Normalize(this)));

return !ReferenceEquals(this, HttpMethod.Get) && !ReferenceEquals(this, HttpMethod.Head) && !ReferenceEquals(this, HttpMethod.Connect) &&
!ReferenceEquals(this, HttpMethod.Options) && !ReferenceEquals(this, HttpMethod.Delete);
}
}
}
}
Loading
Loading