-
Notifications
You must be signed in to change notification settings - Fork 101
Add support for configuring client-wide gRPC binary metadata values, validate metadata earlier #997
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
Add support for configuring client-wide gRPC binary metadata values, validate metadata earlier #997
Conversation
|
|
|
@jazev-stripe I haven't reviewed this since it's still DRAFT. Thanks for contributing though! Anything you need to un-draft? Happy to review it once it is. |
|
@Sushisource sorry about the lack of details; I've been meaning to finish this PR (and the corresponding Python one). I have a thread in the Stripe support channel in the Temporal Slack about this with @cretz where we've discussed the shape of the change, and I think we're mostly aligned [0]. I'm aiming to get this PR in a more reviewable state by the end of the week (once I get a spare couple hours at work🤞). [0] One change I think I'll make based on our discussion is to make |
741c6ba to
deeb57a
Compare
cretz
left a comment
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 LGTM, would like @Sushisource to take a peek before merging
|
|
||
| /// Errors thrown when a gRPC metadata header is invalid. | ||
| #[derive(thiserror::Error, Debug)] | ||
| pub enum InvalidHeaderError { |
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.
Would have also been ok w/ just anyhow::Error (if it works here) to keep it simple since this is rare, but this more verbose one is probably fine too
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.
Happy to switch over to an anyhow::Error if you feel strongly; otherwise I'll leave it as-is
Part of why I biased towards wrapper errors is that the tonic errors don't include the key/value that was invalid (though we could always add one/both to the anyhow error context if we wanted)
| let key = match AsciiMetadataKey::from_str(&k) { | ||
| Ok(key) => key, | ||
| Err(err) => { | ||
| return Err(InvalidHeaderError::InvalidAsciiHeaderKey { |
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 match statement here is somewhat verbose, but I used it instead of .map_err to avoid cloning the key, in the case that we need to return an error
| } | ||
| }; | ||
| let value = match MetadataValue::from_str(&v) { | ||
| Ok(value) => value, |
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.
Same here (for the value)
| source: tonic::metadata::errors::InvalidMetadataKey, | ||
| }, | ||
| /// An ASCII header value was invalid | ||
| #[error("Invalid ASCII header value for key '{key}': {source}")] |
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.
I didn't include the invalid value here because I was a bit worried that including it in error messages may lead to a credentials logging risk for users (if headers are used for security values, for example).
Let me know if that makes sense, or if I should make any changes
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.
Makes sense to me
Sushisource
left a comment
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.
Looking good to me. Thanks for the contribution! Just one potential docstring update.
client/src/lib.rs
Outdated
| /// HTTP headers to include on every RPC call as binary gRPC metadata (typically encoded as | ||
| /// base64). |
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.
Not sure what "typically" means here. Presumably the encoding will be happening when the client transmits them, so could we make a more definitive statement? Or is it dependent on some negotiation with the server?
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.
When I was doing research, I saw https://github.com/grpc/proposal/blob/HEAD/G1-true-binary-metadata.md, which involves sometimes not using base64 for binary headers, if negotiated. However, it's unclear to me if that proposal is actually used in practice (or what the state of it is). It doesn't seem accepted based on the header of the doc, but I also see references to that symbol in the gRPC C++ codebase (possibly in a partially-implemented state).
Either way, I think it's almost always encoded as base64, and whatever the encoding is, it's internal to the gRPC transport mechanism. I'll just remove "typically"; this distinction probably doesn't matter much for consumers. The doc-comments on tonic seem to reflect that it only uses base64 too: https://docs.rs/tonic/latest/tonic/metadata/struct.MetadataValue.html
What was changed
This PR allows configuring gRPC binary metadata key/values (where the key ends in
-bin) on the Temporal client struct. It's already possible for Rust users of the Temporal client struct to set per-request gRPC binary metadata key/values; this allows setting client-wide headers.The main API changes are:
ClientOptions.binary_headers: Option<HashMap<String, Vec<u8>>>ConfiguredClient::set_binary_headers(&self, binary_headers: HashMap<String, Vec<u8>>) -> Result<(), InvalidHeaderError>InvalidHeaderErrorConfiguredClient::set_headers(for setting ASCII headers) now returnsErr(InvalidHeaderError)if the keys or values are invalid, and no headers are modified. Before, it would just silently discard/skip invalid keys or values.ClientOptions::connect(and the related methods) now returns anErr(ClientInitError::InvalidHeaders(InvalidHeaderError))if any of the keys or values passed inClientOptionsare invalidThe approach taken is to parse the metadata keys/values early, and store an already-parsed representation (that can be infallibly applied to the outbound metadata, in
ClientHeaders ::apply_to_metadata).This PR is depended on by temporalio/sdk-python#1070
Why?
#991
(for the specific parts in Core SDK that are needed for the Python SDK; this doesn't cover all Core-SDK changes that are needed (for e.g. the Rust SDK or the C bridge)).
Checklist
ClosesHow was this tested:
client/src/lib.rsAny docs updates needed?