-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Use default interfaces for a more x3 gain in getting and setting headers #31519
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
Conversation
|
Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:
|
cbad508 to
881e4db
Compare
8df0090 to
38ca2a6
Compare
|
Also updated IIS/HttpSys to use fast-path for Request headers as wouldn't want @NickCraver to miss out 😉 |
5e1ee91 to
7a06ae9
Compare
|
I mean the perf is great, but this is just far easier to read as a baseline. Absolute 👍 |
7a06ae9 to
3ceba0d
Compare
|
CI suggests HTTP/2 headers need a bit of patching up with this change; but will wait till outcome of api review |
|
@benaadams Any improvement for gets? |
|
So the main thing we need to resolve with the API is how to represent:
This leads to |
They are the semantics that already exist? e.g. It is specified in aspnetcore/src/Http/Http.Features/src/IHeaderDictionary.cs Lines 14 to 19 in 0aae147
|
I assume similar; though there doesn't seem to be a benchmark for it. Will make one... |
|
Aside I was curious how PGO and GuardedDevirtualization would effect this; so rearranged the benchmark so the setting of the headers was in local function outside loop then upped the iteration count and invocation count and tried some Jit switches
/cc @AndyAyersMS main & PR PGO Devirt |
|
Tweaked benchmark and added Get variant
|
|
I expect most users of PGO will simply end up using: (as GDV is on by default now when PGO on, and disabling R2R is hopefully going to be less important as we'll have static PGO for the key methods that get prejitted)... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you do it this way because of the Http.Features netstandard2.0 target?
Ideally Http.Features would depend on Http.Headers, and that way you don't have to move HeaderNames.
If HeaderNames is moving, you'll need a TypeForward attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you do it this way because of the Http.Features netstandard2.0 target?
Http.Headersisnet6.0onlyHttp.Featuresisnet6.0,netstd2.0andnet461
Ideally Http.Features would depend on Http.Headers,
3 options there?
- Drop support for
netstd2.0andnet461inHttp.Features - Add support for
netstd2.0andnet461inHttp.Headers - Conditionally reference
Http.Headersbut only for thenet6.0version ofHttp.Features
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Option I'd go for is removing netstd2.0 and net461...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The SignalR client consumes the Features package so we can't drop TFMs from it.
- Add support for
netstd2.0andnet461inHttp.Headers
How bad is it? I assume it's using APIs not available on net461, but I can't guess how many. This isn't a library we've aggressively optimized.
- Conditionally reference
Http.Headersbut only for thenet6.0version ofHttp.Features
So the IHeadersDictionary partial would be conditional to net6.0? Does that break anything upstream?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the IHeadersDictionary partial would be conditional to net6.0?
Well neither netstd2.0 or net461 support default interface methods so it would have to be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#31723 will address this by splitting up the Http.Features assembly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After #32043 please switch this around so Http.Features depends on Http.Headers and revert the type move.
5e18d77 to
1f94bc3
Compare
|
Added a tweak to the Pre L0000: xor eax, eax
L0002: mov rdx, [rcx+8]
L0006: test edx, 0x100000
L000c: je short L0012
L000e: mov rax, [rcx+0x30]
L0012: retPost L0000: mov rax, [rcx+0x30]
L0004: test dword ptr [rcx+8], 0x100000
L000b: je short L000e
L000d: ret
L000e: xor eax, eax
L0010: ret |
|
@benaadams you'll want to rebase on top of #32043 |
9bc7c11 to
f31fd63
Compare
|
Rebased and CI passed |
| { | ||
| _genericEnumerator = headers.GetEnumerator(); | ||
| _headersType = HeadersType.Untyped; | ||
| switch (headers) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is interesting. Why did this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
|
||
| namespace Microsoft.AspNetCore.Http | ||
| { | ||
| public partial interface IHeaderDictionary |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the flow for adding a new header?
- Add it to HeaderNames
- Add it to IHeaderDictionary
- Re-run the kestrel code generator, it will pick up the new header from HeaderNames.
- Does anything else need to be updated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes; I had a source generator for the IHeaderDictionary (how I originally generated them); but the CI rejected it as a used project that wasn't in the shipped API, which seemed to be going wrong since it was just internal and not to be shipped.
Could automate it as part of the Kestrel code generator (so only need to add to HeaderNames); however that would need an attribute on some of the headers (e.g. DNT) to say don't add them (Obselete may work or may be too strong; since it also has Path,Scheme, Method etc.)
|
Thanks for waiting for the rebase, and updating all of the usage. |
| var context = TestUtils.CreateTestContext(sink); | ||
| context.HttpContext.Request.Method = HttpMethods.Get; | ||
| context.HttpContext.Request.Headers[HeaderNames.Authorization] = "Basic plaintextUN:plaintextPW"; | ||
| context.HttpContext.Request.Headers.Authorization = "Basic plaintextUN:plaintextPW"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@benaadams @JunTaoLuo can we use the string Placeholder for the pw here? Looks like this introduced a credscan bug (which is surprising since the creds haven't changed).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bonus, so credscan recognises the new Authorization header; whereas it didn't in the old style?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems that way!

Faster header get set by using default interfaces.
Also a cleaner API surface
Results
Test # 4 HeaderCollectionBenchmark
Effected headers in tests:
Content-Type(indirectly viaDefaultHttpRequest.ContentType)Content-Type,Connection,Cache-Control,Vary,Content-Encoding,Expires,Last-Modified,Set-Cookie,ETag,Transfer-Encoding,Content-Language,Upgrade,Via,Access-Control-Allow-Origin,Access-Control-Allow-Credentials,Access-Control-Expose-HeadersContent-Type,Link,X-UA-Compatible,X-Powered-By,X-Content-Type-Options,X-XSS-Protection,X-Frame-Options,Strict-Transport-Security,Content-Security-PolicyExample user code change to take advantage of fast-path (outside of the default features/middleware/mvc)
Snippet from the default interface partial as example
Mechanism
get,settoIHeaderDictionaryfor each header defined inHeaderNames; that calls through to the regularstringlookup on same interfaceget,sets can be implemented by going direct to the field; entirely bypassing the lookup. (Kestrel fdf63ca , IIS/HttpSys d36ab8d)Dictionary<string, StringValues>lookup.This is an RFC as it still would need:
stringlookup and direct is the sameIHeaderDictionaryfromHeaderNames; however including a source generator (e.g. code for benaadams/Ben.Generator.KeyedIDictionary) makes the api checker unhappy as its a used assembly not shipped; it doesn't seem to distinguish between build-time only assemblies and runtime required ones.PublicAPI.Unshipped.txtspilt by framework; asIHeaderDictionarymulti-targets netfx, netstd and net6.0; but only net6.0 supports default interfaces.