Skip to content

Commit

Permalink
Cache more info on HttpMethod (dotnet#100177)
Browse files Browse the repository at this point in the history
* Cache more info on HttpMethod

* Avoid allocating in more cases
  • Loading branch information
MihaZupan authored Mar 24, 2024
1 parent 853bc58 commit d12b12a
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 203 deletions.
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

0 comments on commit d12b12a

Please sign in to comment.