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

feat: client identification headers #89

Open
wants to merge 16 commits into
base: main
Choose a base branch
from

Conversation

kwasniew
Copy link

@kwasniew kwasniew commented Jan 28, 2025

About the changes

This PR adds standardised client identification headers to the feature and metrics calls that the client makes to Unleash. The headers are:

  • x-unleash-appname: the name of the application that is using the client.
  • x-unleash-connection-id: a unique identifier for the current instance of the client generated by the built-in UUID lib
  • x-unleash-sdk: sdk information in the format unleash-client-android:<version>

All the headers are intended for the Unleash team so clients should not be affected.
The main use cases we have are:

  • capacity planning by knowing the number of unique connections made to the backend API
  • debugging misconfigured SDKs sending more traffic than expected

Important files

Discussion points

I added 2 new dependencies deliberately choosing versions that work on 1.60.0. However some transitive dependencies of existing dependencies have changed and require 1.60.3. I found that any dependency update will trigger this error. Maybe we can update MSRV?

Also we have a new way of adding library version at compile time from the Cargo file.

Cargo.toml Outdated Show resolved Hide resolved
@@ -36,10 +43,15 @@ where
Ok(HTTP {
client: C::default(),
app_name,
sdk_version: get_sdk_version(),
connection_id: Uuid::new_v4().into(),
instance_id,
authorization,
authorization_header: C::build_header("authorization")?,
app_name_header: C::build_header("appname")?,
Copy link
Author

Choose a reason for hiding this comment

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

this header will be deprecated in the next version since we're moving towards standardized x-unleash headers

Copy link
Collaborator

Choose a reason for hiding this comment

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

I haven't seen these headers before. Are they already in production somewhere?

Copy link
Collaborator

Choose a reason for hiding this comment

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

If not, please see https://datatracker.ietf.org/doc/html/rfc6648 - x- is a deprecated pattern and we SHOULD NOT introduce it now.

Copy link
Author

@kwasniew kwasniew Jan 29, 2025

Choose a reason for hiding this comment

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

We started adding them 2 weeks ago for our internal purposes. Those are not useful to Unleash users and only used internally so we have more freedom in changing them in future. You raise a good point with the x-prefix. I'll discuss it with the team tomorrow (if it's worth changing it given all other SDKs already support it). We may have some legacy users who require the x- prefix in their network infra.

Copy link
Author

Choose a reason for hiding this comment

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

Update: after internal discussions we decided to drop the x- prefix and we'll stick to unleash-. The spec link you sent is convincing :)

@@ -101,7 +102,7 @@ impl Default for Registration {
Self {
app_name: "".into(),
instance_id: "".into(),
sdk_version: "unleash-client-rust-0.1.0".into(),
Copy link
Author

Choose a reason for hiding this comment

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

  • this version was out of sync with the cargo version
  • also in out backend we assume a different format unleash-client-rust:version
    I decided to create a function capturing those requirements

src/version.rs Outdated
@@ -0,0 +1,23 @@
use std::env;

// include version into the binary at compile time
Copy link
Author

Choose a reason for hiding this comment

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

It is very important to read the version env var at compile time so that it's hardcoded into the final binary for predictability and reading the library version and not the version of the application using this library

src/http.rs Outdated
@@ -36,10 +43,15 @@ where
Ok(HTTP {
client: C::default(),
app_name,
sdk_version: get_sdk_version(),
connection_id: Uuid::new_v4().into(),
Copy link
Author

Choose a reason for hiding this comment

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

I decided to instantiate the UUID when we create our HTTP client since we don't need it anywhere else. Since this is intended for internal tracking purposes of SDK HTTP connection and users should not override it I put it here instead of passing it from the outside.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Does this make sense for the Rust SDK as-is? A new UUID every process restart doesn't provide much value. (And indeed for k8s we'd see 10's of thousands of clients a week...

Copy link
Author

Choose a reason for hiding this comment

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

We've been using it for 2 weeks already and it provides a lot of value to us on the server side. We want to know how many instances are created. We track it in hourly buckets with hyperloglog so very high numbers are not a problem and they help us in capacity planning and debugging of misconfigured SDK clients (with per request scope objects)

src/version.rs Outdated Show resolved Hide resolved
Cargo.toml Outdated
@@ -62,6 +62,7 @@ rustversion = "1.0.7"
serde_json = "1.0.68"
serde_plain = "1.0.0"
surf = { version = "2.3.1", optional = true }
uuid = { version = "0.8.2", features = ["v4"] }
Copy link
Author

Choose a reason for hiding this comment

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

highest version running on rustc 1.60.0. I also considered embedding uuid generation function into the library itself but my first option is to use a lib.

@@ -62,6 +62,7 @@ rustversion = "1.0.7"
serde_json = "1.0.68"
serde_plain = "1.0.0"
surf = { version = "2.3.1", optional = true }
uuid = { version = "1.11.0", features = ["v4"] }
Copy link
Author

Choose a reason for hiding this comment

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

latest 1.60.0 supported version

@@ -91,6 +92,7 @@ futures = "0.3.17"
maplit = "1.0.2"
num_cpus = "1.13.0"
simple_logger = "2.1.0"
regex = "1.9.6"
Copy link
Author

Choose a reason for hiding this comment

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

latest 1.60.0 supported version

Copy link
Member

@chriswk chriswk left a comment

Choose a reason for hiding this comment

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

I'm a 👍 here.

@chriswk
Copy link
Member

chriswk commented Jan 29, 2025

I'm a 👍 here.

but we will have to hash out the MSRV details. I know @sighphyre is already looking

Copy link
Collaborator

@rbtcollins rbtcollins left a comment

Choose a reason for hiding this comment

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

I will read the code more shortly, but a quick note:

x- headers are deprecated.

x-unleash-appname: the name of the application that is using the client.
x-unleash-connection-id: a unique identifier for the current instance of the client generated by the built-in UUID lib
x-unleash-sdk: sdk information in the format unleash-client-android:

should instead be something without x-.

src/version.rs Outdated Show resolved Hide resolved
src/version.rs Outdated Show resolved Hide resolved
instance_id: String,
connection_id: String,
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is being used as a client id, not per-connection

Suggested change
connection_id: String,
client_id: String,

Copy link
Author

Choose a reason for hiding this comment

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

We have an established convention across all SDKs so I'd prefer to have one name for this. We call it connection_id because we're also adding streaming support and we want to treat a long lived SSE stream and interval/polling connections from the same instance as one logical connection.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree that consistency matters, however it is still mismatched with the code behaviour.

Personally, I would name it differently to capture these nuances in the other clients, but thats out of my hands :)

I will ask for a comment though, explaining why this is considered a 'connection id', even though its not per HTTP connection, and not unique per process.

@rbtcollins
Copy link
Collaborator

This has no test coverage; someone could change it later and we'd have nothing capturing what or why they shouldn't.

@kwasniew
Copy link
Author

kwasniew commented Jan 29, 2025

@rbtcollins Thanks for a thorough review.
I added tests checking the new headers and existing ones.
I'm also interested in your take on the MSRV bump.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

3 participants