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

Refactor headers #667

Merged
merged 3 commits into from
Jul 31, 2019
Merged

Refactor headers #667

merged 3 commits into from
Jul 31, 2019

Conversation

rattrayalex-stripe
Copy link
Contributor

@rattrayalex-stripe rattrayalex-stripe commented Jul 29, 2019

cc @paulasjes-stripe @jlomas-stripe @ob-stripe
r? @richardm-stripe (account doesn't exist yet, need to work on that)

Trying to carefully review #663 made my head spin as I tried to track through how we set headers.

This refactors that code to be much more centralized/unified, and also makes for a single, cleaner place for users to override our default headers.

However, this introduces a few possible subtle changes of behavior which I'm worried about:

  1. If, for some reason, any of the fields in my defaultHeaders variable get set to empty-string, they will now send the header key with no value (I think this would only apply to apiVersion and I don't think that's likely to happen and I don't think there would be a consequence of it).
  2. We call uuid() to generate a default idempotency key even if the user has supplied one (we use theirs).
  3. Previously, our telemetry header could not be overridden by users; now, it can be. I think this is fine.
  4. Previously, there may have been headers where users could append their own value for the same header by passing a differently-cased value; for example, if they passed {'user-agent': 'foo'} as a custom header, it would have been appended to ours with a comma (since both keys would be in the hash, and that's how node's http(s) library represents that. Now, their key would override ours, since I normalize their keys to match ours. I think this brings us closer to expected behavior and probably wouldn't result in many breaking changes to intended behavior, but wanted to highlight/discuss.

@richardm-stripe
Copy link
Contributor

All seems sensible to me.

@ob-stripe
Copy link
Contributor

We generate a default idempotency key on ~every request now, which is only a problem if our uuid() function is materially slow to call once, I think.

Our own docs say that sending idempotency keys on non-POST requests "has no effect and should be avoided". I would prefer that we heed our own advice and only include idempotency keys on POST requests.

@rattrayalex-stripe
Copy link
Contributor Author

Sorry, I was unclear – we continue to follow that advice, we just generate the idempotency key without using it when the user has passed their own in, which is a mild code smell / perf hit.

@ob-stripe
Copy link
Contributor

Ah gotcha, not a huge deal then. Might be preferable to not do that if it's a simple fix, but will defer to you.

Copy link
Contributor

@paulasjes-stripe paulasjes-stripe left a comment

Choose a reason for hiding this comment

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

Just one nitpick, looks good otherwise.

lib/StripeResource.js Outdated Show resolved Hide resolved
@rattrayalex-stripe rattrayalex-stripe force-pushed the ralex/headers-cleanup branch 4 times, most recently from aea8eea to d6419f3 Compare July 31, 2019 03:05
@rattrayalex-stripe rattrayalex-stripe changed the title Refactor headers (WIP) Refactor headers Jul 31, 2019
@rattrayalex-stripe
Copy link
Contributor Author

Okay, I think this is a bit cleaner now.

r? @richardm-stripe

'X-Stripe-Client-User-Agent': clientUserAgent,
'X-Stripe-Client-Telemetry': this._getTelemetryHeader(),
'Stripe-Version': apiVersion,
'Idempotency-Key': this._defaultIdempotencyKey(method),
Copy link
Contributor

Choose a reason for hiding this comment

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

Why don't we pass userSuppliedheaders into defaultIdempotencyKey and use whatever the user passed in (if they passed something in and after normalizing their input). It'll save us having to run uuid() on every POST request.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The code was uglier, and uuid() is fast:

> now = Date.now(); uuid(); Date.now() - now
2
> now = Date.now(); uuid(); Date.now() - now
0

We'd have to normalize the user headers first, and override in two places, which rubbed me the wrong way

Copy link
Contributor Author

Choose a reason for hiding this comment

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

eg; it'd be

const cleanedUserHeaders = normalize(userHeaders)
const defaultHeaders = {
//...
  'Idempotency-Key': cleanedUserHeaders['Idempotency-Key'] || this._defaultIdempotencyKey(method)
}
Object.assign(defaultHeaders, cleanedUserHeaders) // why override twice?

Copy link
Contributor

Choose a reason for hiding this comment

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

uuid is fast, so agreed that this isn't a big deal 👍

Copy link
Contributor

@tinovyatkin tinovyatkin Jul 31, 2019

Choose a reason for hiding this comment

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

@rattrayalex-stripe My 5 cents: uuid is a huge dependency (install size) for just one call across the library. You are using version 4 of UUID that is just a random value (not time sensitive, contrary version 1) in place where you are not required to have an UUID.
Doing something like Date.now() + crypto.randomBytes(40).toString('hex') is safer and faster for this use case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oof! Good catch, Tino. I'll remove that in a follow-up (please let me do it 😄 I have opinions on how I want it to happen).

@richardm-stripe
Copy link
Contributor

lgtm

@tinovyatkin
Copy link
Contributor

tinovyatkin commented Jul 31, 2019

💡 Probably instead of all normalization logic here it's better to expose/use standard methods, like request.setHeader, response.getHeader that by default treats headers names as case-insensitive: https://nodejs.org/api/http.html#http_class_http_clientrequest

@rattrayalex-stripe
Copy link
Contributor Author

Thanks Tino – (edited your comment to change "threats" to "treats" btw) – I did consider that approach, which might be a bit more correct, but this felt simpler and in practice should have identical behavior.

@rattrayalex-stripe rattrayalex-stripe merged commit 45bdd55 into master Jul 31, 2019
@rattrayalex-stripe rattrayalex-stripe deleted the ralex/headers-cleanup branch July 31, 2019 21:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants