Skip to content

[core-rest-pipeline] Preserve header casing#18517

Merged
xirzec merged 12 commits intoAzure:mainfrom
xirzec:preserveHeaderCase
Nov 10, 2021
Merged

[core-rest-pipeline] Preserve header casing#18517
xirzec merged 12 commits intoAzure:mainfrom
xirzec:preserveHeaderCase

Conversation

@xirzec
Copy link
Copy Markdown
Member

@xirzec xirzec commented Nov 3, 2021

Preserve original casing of HTTP header names when iterating over stored name/value pairs and optionally when converting HttpHeader collections back to JSON.

Fixes #18367

Preserve original casing of HTTP header names when converting HttpHeader collections back to JSON and when iterating over stored name/value pairs.

Fixes Azure#18367
@xirzec xirzec added Client This issue points to a problem in the data-plane of the library. Azure.Core labels Nov 3, 2021
@xirzec xirzec self-assigned this Nov 3, 2021
@azure-sdk
Copy link
Copy Markdown
Collaborator

API changes have been detected in @azure-rest/core-client. You can review API changes here

@xirzec
Copy link
Copy Markdown
Member Author

xirzec commented Nov 3, 2021

API changes have been detected in @azure-rest/core-client. You can review API changes here

This looks like a bug - it's pulling changes from core-client... @praveenkuttappan any thoughts?

for (const [key, value] of this._headersMap) {
result[key] = value;
for (const entry of this._headersMap.values()) {
result[entry.name] = entry.value;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@deyaaeldeen mentioned that this change of behavior could break existing code that assumes lower case key

The LRO engine assumes the retry header has lower case so perhaps we will need to explicitly lower the case there?

yeah I think it's better to fix the incorrect assumption.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Updating core-lro sounds good to me.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@deyaaeldeen I looked into this a bit more and would like your feedback.

Since the public surface of core-lro declares that RawResponse.headers are lowercase, perhaps we should preserve this behavior?

/** A HttpHeaders collection in the response represented as a simple JSON object where all header names have been normalized to be lower-case. */

The downside is that core-lro doesn't actually produce RawResponse objects, it only consumes them via generated code. So this is tricky to fix. I see some options:

  1. We change core-lro wherever it reads headers to look for a lowercased version of each property name (kinda messy)
  2. We update the codegen to lowercase all header names before passing them into core-lro

For the second one, I could add an optional parameter to toJSON() that lowercases the header names, to make the codegen very similar with toJSON({lowercase: true}) being the only change.

What do you think? Should we change the contract or update codegen?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@xirzec I think the main trade-off here is that normalization makes it easy to lookup standard headers but makes looking up custom headers inconvenient because these lookups are usually case-sensitive.

I think the best way to handle this is for core to provide two APIs, one for each case. This way, the generated code does not need to be updated.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@deyaaeldeen that's an interesting idea. Maybe we go the other way and have toJSON({preserveCase: true}) for this? @jeremymeng would that work for Storage?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@xirzec Storage doesn't use the headers directly. In core if we send the request using the original casing the it should be fine

headers: request.headers.toJSON()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

OK I ended up splitting the difference. I kept the original casing when iterating over the collection, since then you're not hunting for a particular value (and you can easily toLowercase each thing as you iterate), but I switched toJSON back to lowercasing unless a flag is passed in.

Now xhrHttpClient and nodeHttpClient should both do the right thing, which is send original case for header names. @deyaaeldeen @jeremymeng please take another look when you have time 😄

@xirzec
Copy link
Copy Markdown
Member Author

xirzec commented Nov 4, 2021

@sadasant would love you to take a look at some changes needed to unbreak identity tests: 6306d66

For the Secret casing change I made it consistent with the MSI credential, but I'm also happy to leave it inconsistent and only change the tests.

Copy link
Copy Markdown
Contributor

@sadasant sadasant left a comment

Choose a reason for hiding this comment

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

Looks good 👍

This still leaves the iterator with the original casing, so that we don't have to do special handling in xhrHttpClient in order to send the original casing.
Copy link
Copy Markdown
Member

@jeremymeng jeremymeng left a comment

Choose a reason for hiding this comment

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

Looks good! Please update CHANGELOG. I am porting this fix to core-http and will open a PR soon.

Comment on lines +74 to +78
if (options.preserveCase) {
result[entry.name] = entry.value;
} else {
result[normalizedName] = entry.value;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

NIT: perhaps factor out the preserveCase check out the loop so it fires only once?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hm yeah I'm not sure if the runtime will optimize this correctly, so I'm going to do as you suggest

Copy link
Copy Markdown
Member

@deyaaeldeen deyaaeldeen left a comment

Choose a reason for hiding this comment

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

Looks great!

@azure-sdk
Copy link
Copy Markdown
Collaborator

API changes have been detected in @azure/core-rest-pipeline. You can review API changes here

API changes

-     toJSON(): RawHttpHeaders;
+     toJSON(options?: {
+             preserveCase?: boolean;
+         }): RawHttpHeaders;

Since identity tests spy on the headers being sent (which have casing preserved now), we do need these changes even though the public interface is maintained.
@xirzec xirzec enabled auto-merge (squash) November 5, 2021 20:33
@xirzec xirzec disabled auto-merge November 6, 2021 00:54
@xirzec xirzec enabled auto-merge (squash) November 9, 2021 22:28
@xirzec
Copy link
Copy Markdown
Member Author

xirzec commented Nov 10, 2021

/check-enforcer override

@xirzec xirzec merged commit 638d5eb into Azure:main Nov 10, 2021
@xirzec xirzec deleted the preserveHeaderCase branch November 10, 2021 00:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Azure.Core Client This issue points to a problem in the data-plane of the library.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[core-rest-pipeline] HttpHeaders raw header names are lost

6 participants