Skip to content
This repository has been archived by the owner on Dec 18, 2018. It is now read-only.

Use enum for method rather than string compares #2294

Merged
merged 4 commits into from
Feb 23, 2018

Conversation

benaadams
Copy link
Contributor

@benaadams benaadams commented Feb 3, 2018

For GET method IsHead test is performed on the string Method which since GET isn't reference equal to HEAD drops through to String.Equals -> OrdinalIgnoreCaseComparer.Equals

However the Parser starts with it as an Enum, which then gets converted to a string; so it can just be tested with the enum.

Rebased #2027

Resolves: #2020

_httpVersion = version;

Debug.Assert(RawTarget != null, "RawTarget was not set");
Debug.Assert(Method != null, "Method was not set");
Debug.Assert(((IHttpRequestFeature)this).Method != null, "Method was not set");
Copy link
Member

Choose a reason for hiding this comment

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

Why this change?

Copy link
Member

Choose a reason for hiding this comment

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

Ah, because method is an enum.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To get the string version

@benaadams
Copy link
Contributor Author

OSX

Failed   RegisterAddresses_IPv6_Success(addressInput: "http://*:0/", testUrls: ["http://127.0.0.1", "http://[::1]"])
Error Message:
 System.Net.Sockets.SocketException : Operation timed out
Stack Trace:
   at Microsoft.AspNetCore.Testing.HttpClientSlim.GetSocket(Uri requestUri)
   at Microsoft.AspNetCore.Testing.HttpClientSlim.GetStream(Uri requestUri, Boolean validateCertificate)
   at Microsoft.AspNetCore.Testing.HttpClientSlim.GetStringAsync(Uri requestUri, Boolean validateCertificate)
   at Microsoft.AspNetCore.Testing.HttpClientSlim.GetStringAsync(String requestUri, Boolean validateCertificate)
   at Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.AddressRegistrationTests.RegisterAddresses_Success(String addressInput, String[] testUrls, Int32 testPort) in /_/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs:line 179
   at Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.AddressRegistrationTests.RegisterAddresses_IPv6_Success(String addressInput, String[] testUrls) in /_/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs:line 121

@benaadams
Copy link
Contributor Author

Reworded commit to retrigger CI

throw new ArgumentException(nameof(value));
}

var firstChar = value[0];
Copy link
Member

@davidfowl davidfowl Feb 4, 2018

Choose a reason for hiding this comment

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

Why this implementation if its not performance sensitive? Just use the http abstractions methods

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is used by http2 so may be perf sensitive :-/

Also are aspnet dlls ngen'd in the newest versions? Does mean none of the methods can be inlined if so.

Did you mean

if (HttpMethods.IsGet(value))
{
    method = HttpMethod.Get;
    return true;
}

Copy link
Member

Choose a reason for hiding this comment

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

How is this used by HTTP/2? If this is only hit by applications setting the request method themselves, I too would prefer a more readable slightly slower version of this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

HTTP/2 goes via string header

((IHttpRequestFeature)this).Method = RequestHeaders[":method"];

Though it probably shouldn't use an interface cast for it https://github.com/dotnet/coreclr/issues/16198

Copy link
Contributor Author

@benaadams benaadams Feb 5, 2018

Choose a reason for hiding this comment

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

i.e. first line of Http2Stream.TryParseRequest(ReadResult result, out bool endConnection)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Improved the TryParseRequest not to cast via interface and use method directly

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also changed to use the HttpMethods strings

return _customMethod;
}

_customMethod = HttpUtilities.MethodToString(Method);
Copy link
Member

Choose a reason for hiding this comment

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

The old code did HttpUtilities.MethodToString(method) ?? string.Empty. I'd either keep that or have MethodToString throw instead of return null neither of which should ever actually happen in practice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

throw new ArgumentException(nameof(value));
}

var firstChar = value[0];
Copy link
Member

Choose a reason for hiding this comment

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

How is this used by HTTP/2? If this is only hit by applications setting the request method themselves, I too would prefer a more readable slightly slower version of this method.

@@ -324,7 +325,8 @@ public void Reset()
MaxRequestBodySize = ServerOptions.Limits.MaxRequestBodySize;
AllowSynchronousIO = ServerOptions.AllowSynchronousIO;
TraceIdentifier = null;
Method = null;
Method = HttpMethod.Get;
Copy link
Member

Choose a reason for hiding this comment

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

HttpMethod.None

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Had to change None = byte.MaxValue as so indexing and hashing uses the values

return _customMethod;
}

_customMethod = HttpUtilities.MethodToString(Method);
Copy link
Member

Choose a reason for hiding this comment

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

Why store this in _costomMethod? It's a static lookup. You store null for Set.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

GetKnownMethod returns an HttpMethod enum for known methods, but not the string so null.

The field is there anyway for a non-known method, so once you've looked it up once, might as well store it if its checked again rather than doing another lookup,

Copy link
Member

@Tratcher Tratcher Feb 5, 2018

Choose a reason for hiding this comment

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

So why didn't you store the value in the Set code path?

Copy link
Member

Choose a reason for hiding this comment

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

If you're going to use it for both then call it _methodText

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh.. that's a very good point 😄

/// The Known Methods (CONNECT, DELETE, GET, HEAD, PATCH, POST, PUT, OPTIONS, TRACE)
/// </remarks>
/// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns>
public static bool GetKnownMethod(string value, out HttpMethod method)
Copy link
Member

@JamesNK JamesNK Feb 6, 2018

Choose a reason for hiding this comment

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

Should this be called TryGetKnownMethod? It is following the try pattern of returning a bool with an out param.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reverted, always returns value in out

Copy link
Member

Choose a reason for hiding this comment

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

What's the bool for then?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah good point can clean this up; return the string

@benaadams
Copy link
Contributor Author

K, done - I think

return method != HttpMethod.Custom;
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe HttpMethod GetKnownMethod(byte* data, int length, out int methodLength)
internal static unsafe HttpMethod TryGetKnownMethod(byte* data, int length, out int methodLength)
Copy link
Member

Choose a reason for hiding this comment

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

This shouldn't be Try

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because it always returns a value in out?

Reverted

@benaadams
Copy link
Contributor Author

K, now done... hopefully :)

/// The Known Methods (CONNECT, DELETE, GET, HEAD, PATCH, POST, PUT, OPTIONS, TRACE)
/// </remarks>
/// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns>
public static string GetKnownMethod(string value, out HttpMethod method)
Copy link
Member

Choose a reason for hiding this comment

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

This method is taking string value and then returning it back again. Why not just return HttpMethod instead of an out param?

Also XML docs need to be updated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

}
set
{
Method = HttpUtilities.GetKnownMethod(value);
Copy link
Member

Choose a reason for hiding this comment

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

Out of curiosity, what would happen if we treated every request method set by the app via IHttpRequestFeature.Method as a custom method whether or not it known in order to elide this call to GetKnownMethod?

I don't see how this could be a problem, because none of Kestrel's logic should be predicated on the request method set by the application.

Copy link
Contributor Author

@benaadams benaadams Feb 17, 2018

Choose a reason for hiding this comment

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

So the app couldn't change request behavior based on method? If you changed method from GET -> HEAD the would mean if the app produced no body, but a content length it would throw with an 500 error.

However that may be better than responding with a HEAD response to a GET request and then hanging the browser while waiting for data that would never come?

Copy link
Member

@Tratcher Tratcher Feb 17, 2018

Choose a reason for hiding this comment

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

While the app should be able to change all of the properties of HttpContext to lie to itself, the server must maintain an internal source of truth so it can properly interact with the client.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change is really messing up the tests :(

@benaadams
Copy link
Contributor Author

Changed and rebased

@benaadams benaadams force-pushed the enum-method branch 3 times, most recently from 35d5e16 to 7c528d4 Compare February 23, 2018 01:38
@benaadams
Copy link
Contributor Author

Rebased

Copy link
Member

@halter73 halter73 left a comment

Choose a reason for hiding this comment

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

Thanks! LGTM.

@halter73
Copy link
Member

@pakrym you want to take a look?

@davidfowl
Copy link
Member

@benaadams can you rebase?

/// <returns><see cref="HttpMethod"/></returns>
public static HttpMethod GetKnownMethod(string value)
{
// Called by http/2 and if user code overrides and sets the Method on IHttpRequestFeature
Copy link
Member

Choose a reason for hiding this comment

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

This is only used for http2 now, not setting the method directly right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, will change comment

@benaadams
Copy link
Contributor Author

Rebased, changed comment

Copy link
Member

@davidfowl davidfowl left a comment

Choose a reason for hiding this comment

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

LGTM

@halter73 halter73 merged commit de7e2a2 into aspnet:dev Feb 23, 2018
@halter73
Copy link
Member

Thanks!

@sebastienros
Copy link
Member

7% improvement on plaintext running on our physical.

image

@benaadams
Copy link
Contributor Author

😄 What does the CF mean on those graphs?

@sebastienros
Copy link
Member

Old benchmark to analyze the cost of connection adapters.

Used here: https://github.com/aspnet/benchmarks/blob/dev/src/Benchmarks/Program.cs#L234
With this filter: https://github.com/aspnet/benchmarks/blob/dev/src/Benchmarks/PassthroughConnectionFilter.cs#L12

@halter73
Copy link
Member

This really shows what a difference one extra string comparison per request makes. I think (object.ReferenceEquals(context.ServerSetMethod, HttpMethods.Head)) would have also worked, but moving to enum comparisons was much cleaner.

Thanks @benaadams for finding and fixing this.

@benaadams benaadams deleted the enum-method branch February 26, 2018 02:06
@benaadams
Copy link
Contributor Author

Don't think its just down to this one as there were 5 PRs merged at the same time; but glad they had an effect 😄

Use enum for method rather than string compares (#2294)
Sanitize and centralize exception throws (#2293)
Flatten exception handling (#2313)
Speed up TryGetAsciiString (#1973)
Faster IFeatureCollection.Get (#2290)

@benaadams
Copy link
Contributor Author

Also none of them in the area of recent regression, so everything still to play for 😉

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants