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

experimental: sse client support #592

Merged
merged 57 commits into from
Dec 12, 2024
Merged

experimental: sse client support #592

merged 57 commits into from
Dec 12, 2024

Conversation

thomasheartman
Copy link
Contributor

@thomasheartman thomasheartman commented Dec 2, 2024

⚠️ This is experimental and a work in progress

Overview

tl;dr: This is a very experimental implementation of streaming in Edge: it both listens to the experimental Unleash streaming endpoint and pushes updates to any listeners subscribing to Edge (in effect mirroring the Unleash endpoint). All related code is behind the "streaming" feature gate.

More detail:

I have added an event source client to the features_refresher file. If the streaming flag is on, we'll spawn a task that takes care of listening to Unleash for events instead of starting the poll loop for flags.

There is a new endpoint at api/client/streaming (mirroring the Unleash endpoint) that you can hit to start listening for updates.

The updates are handled by a new broadcaster module (largely stolen from this Actix example).

The broadcaster stores its client in a hash map that uses the flag query as the key and maps it to a vec of clients that use the same query.

Left to do

Regarding the implementation: I'm not very familiar with Actix, and I haven't done a whole lot of async / multithreaded rust before, so there's probably gonna be a whole lot of things that we can improve, from the architecture level to the specific data structures used.

Also, due to the very time-limited spike we did, we need to actually do some real error handling. There's a good few places where we either ignore errors or would just panic if we ever encountered them.

But aside from that, there's a few other things to do too:

  • Store the streaming url in the urls struct
  • Add fetch mode streaming/polling as a CLI option
  • The broadcaster needs to store the token used with each client; not just the query (you can have multiple tokens with the same query and those tokens can be invalidated separately)
  • We should probably find a more sensible way to use the query as a key in the hash map: serializing it and hashing the string seems roundabout and wonky.

Copy link

github-actions bot commented Dec 2, 2024

Dependency Review

The following issues were found:
  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ❌ 1 package(s) with invalid SPDX license definitions
  • ⚠️ 1 package(s) with unknown licenses.
See the Details below.

License Issues

Cargo.lock

PackageVersionLicenseIssue Type
csv1.3.1Unlicense/MITInvalid SPDX License

server/Cargo.toml

PackageVersionLicenseIssue Type
eventsource-client>= 0.13.0, < 0.14.0NullUnknown License
Denied Licenses: GPL-1.0, GPL-2.0, GPL-3.0, LGPL-2.1, LGPL-3.0, AGPL-3.0

OpenSSF Scorecard

Scorecard details
PackageVersionScoreDetails
cargo/actix-web-lab 0.23.0 UnknownUnknown
cargo/actix-web-lab-derive 0.23.0 UnknownUnknown
cargo/csv 1.3.1 🟢 3.8
Details
CheckScoreReason
Code-Review⚠️ 2Found 6/22 approved changesets -- score normalized to 2
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Maintained⚠️ 23 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 2
Binary-Artifacts🟢 10no binaries found in the repo
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Packaging⚠️ -1packaging workflow not detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Security-Policy⚠️ 0security policy file not detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Vulnerabilities🟢 100 existing vulnerabilities detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
cargo/csv-core 0.1.11 🟢 3.8
Details
CheckScoreReason
Code-Review⚠️ 2Found 6/22 approved changesets -- score normalized to 2
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Maintained⚠️ 23 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 2
Binary-Artifacts🟢 10no binaries found in the repo
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Packaging⚠️ -1packaging workflow not detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Security-Policy⚠️ 0security policy file not detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Vulnerabilities🟢 100 existing vulnerabilities detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
cargo/derive_more 1.0.0 🟢 5.2
Details
CheckScoreReason
Code-Review🟢 5Found 16/30 approved changesets -- score normalized to 5
Maintained🟢 93 commit(s) and 8 issue activity found in the last 90 days -- score normalized to 9
Packaging⚠️ -1packaging workflow not detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Security-Policy⚠️ 0security policy file not detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Binary-Artifacts🟢 10no binaries found in the repo
License🟢 10license file detected
Vulnerabilities🟢 100 existing vulnerabilities detected
Signed-Releases⚠️ -1no releases found
Fuzzing⚠️ 0project is not fuzzed
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
cargo/derive_more-impl 1.0.0 🟢 5.2
Details
CheckScoreReason
Code-Review🟢 5Found 16/30 approved changesets -- score normalized to 5
Maintained🟢 93 commit(s) and 8 issue activity found in the last 90 days -- score normalized to 9
Packaging⚠️ -1packaging workflow not detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Security-Policy⚠️ 0security policy file not detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Binary-Artifacts🟢 10no binaries found in the repo
License🟢 10license file detected
Vulnerabilities🟢 100 existing vulnerabilities detected
Signed-Releases⚠️ -1no releases found
Fuzzing⚠️ 0project is not fuzzed
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
cargo/eventsource-client 0.13.0 🟢 5.3
Details
CheckScoreReason
Binary-Artifacts🟢 10no binaries found in the repo
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Code-Review🟢 9Found 16/17 approved changesets -- score normalized to 9
Security-Policy🟢 9security policy file detected
Maintained⚠️ 00 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 0
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Packaging⚠️ -1packaging workflow not detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
License🟢 9license file detected
Fuzzing⚠️ 0project is not fuzzed
Vulnerabilities🟢 100 existing vulnerabilities detected
Signed-Releases⚠️ -1no releases found
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
cargo/hyper-timeout 0.4.1 🟢 4.2
Details
CheckScoreReason
Code-Review🟢 3Found 5/13 approved changesets -- score normalized to 3
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Binary-Artifacts🟢 10no binaries found in the repo
Packaging⚠️ -1packaging workflow not detected
Maintained🟢 57 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 5
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Security-Policy⚠️ 0security policy file not detected
Vulnerabilities🟢 100 existing vulnerabilities detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 9license file detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
cargo/mediatype 0.19.18 UnknownUnknown
cargo/pin-project 1.1.7 🟢 5.9
Details
CheckScoreReason
Binary-Artifacts🟢 10no binaries found in the repo
Code-Review⚠️ 0Found 0/30 approved changesets -- score normalized to 0
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Packaging⚠️ -1packaging workflow not detected
Maintained🟢 1029 commit(s) and 3 issue activity found in the last 90 days -- score normalized to 10
Token-Permissions🟢 10GitHub workflow tokens follow principle of least privilege
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Vulnerabilities🟢 100 existing vulnerabilities detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration
Security-Policy⚠️ 0security policy file not detected
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
cargo/pin-project-internal 1.1.7 🟢 5.9
Details
CheckScoreReason
Binary-Artifacts🟢 10no binaries found in the repo
Code-Review⚠️ 0Found 0/30 approved changesets -- score normalized to 0
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Packaging⚠️ -1packaging workflow not detected
Maintained🟢 1029 commit(s) and 3 issue activity found in the last 90 days -- score normalized to 10
Token-Permissions🟢 10GitHub workflow tokens follow principle of least privilege
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Vulnerabilities🟢 100 existing vulnerabilities detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration
Security-Policy⚠️ 0security policy file not detected
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
cargo/serde_html_form 0.2.6 UnknownUnknown
cargo/serde_path_to_error 0.1.16 🟢 4.7
Details
CheckScoreReason
Maintained⚠️ 12 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 1
Code-Review⚠️ 0Found 1/29 approved changesets -- score normalized to 0
Binary-Artifacts🟢 10no binaries found in the repo
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Packaging⚠️ -1packaging workflow not detected
Token-Permissions🟢 10GitHub workflow tokens follow principle of least privilege
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Fuzzing⚠️ 0project is not fuzzed
Vulnerabilities🟢 100 existing vulnerabilities detected
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
Security-Policy🟢 3security policy file detected
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
cargo/tokio-io-timeout 1.2.0 🟢 3.3
Details
CheckScoreReason
Packaging⚠️ -1packaging workflow not detected
Dangerous-Workflow⚠️ -1no workflows found
Binary-Artifacts🟢 10no binaries found in the repo
Code-Review⚠️ 2Found 6/21 approved changesets -- score normalized to 2
Pinned-Dependencies⚠️ -1no dependencies found
Token-Permissions⚠️ -1No tokens found
Maintained⚠️ 00 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Vulnerabilities🟢 100 existing vulnerabilities detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
Security-Policy⚠️ 0security policy file not detected
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
cargo/actix-web-lab >= 0.23.0, < 0.24.0 UnknownUnknown
cargo/eventsource-client >= 0.13.0, < 0.14.0 🟢 5.3
Details
CheckScoreReason
Binary-Artifacts🟢 10no binaries found in the repo
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Code-Review🟢 9Found 16/17 approved changesets -- score normalized to 9
Security-Policy🟢 9security policy file detected
Maintained⚠️ 00 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 0
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Packaging⚠️ -1packaging workflow not detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
License🟢 9license file detected
Fuzzing⚠️ 0project is not fuzzed
Vulnerabilities🟢 100 existing vulnerabilities detected
Signed-Releases⚠️ -1no releases found
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
cargo/tokio-stream >= 0.1.16, < 0.2.0 🟢 8.3
Details
CheckScoreReason
Code-Review🟢 10all changesets reviewed
Maintained🟢 1030 commit(s) and 3 issue activity found in the last 90 days -- score normalized to 10
Security-Policy🟢 10security policy file detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Packaging⚠️ -1packaging workflow not detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Binary-Artifacts🟢 10no binaries found in the repo
Token-Permissions🟢 10GitHub workflow tokens follow principle of least privilege
Vulnerabilities🟢 100 existing vulnerabilities detected
License🟢 10license file detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration
Fuzzing🟢 10project is fuzzed
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0

Scanned Files

  • Cargo.lock
  • server/Cargo.toml

server/Cargo.toml Outdated Show resolved Hide resolved
server/Cargo.toml Outdated Show resolved Hide resolved
server/src/client_api.rs Outdated Show resolved Hide resolved
server/src/http/feature_refresher.rs Outdated Show resolved Hide resolved
server/src/http/feature_refresher.rs Outdated Show resolved Hide resolved
server/src/http/feature_refresher.rs Outdated Show resolved Hide resolved
server/src/http/feature_refresher.rs Outdated Show resolved Hide resolved
server/src/http/broadcaster.rs Show resolved Hide resolved
server/src/http/broadcaster.rs Outdated Show resolved Hide resolved
}
}
})
.map_err(|e| warn!("Error in SSE stream: {:?}", e));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

handle 404s etc, maybe launch polling if can't connect

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a more complex topic. I suggest we move handling this one to a little later.

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.

After the Google meet review, I'm comfortable with giving you a 👍 on this one. Merge when you feel you've made enough of the nitpick changes.

I'm ready for a pairing session on getting some tests in place later this week or next.

Copy link
Member

@nunogois nunogois left a comment

Choose a reason for hiding this comment

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

I think you did an amazing job with this! 🥇


#[cfg(feature = "streaming")]
#[get("/streaming")]
pub async fn stream_features(
Copy link
Member

Choose a reason for hiding this comment

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

Maybe this would be slightly more consistent with Unleash's counterpart?

Suggested change
pub async fn stream_features(
pub async fn connect(

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure. But as the API endpoint, maybe it should be more explicit? Feels like stream_features is clearer to me. But then again, it's a separate controller in Unleash. I don't care much either way, but this feels more explicit in this context.

impl From<SendError<Event>> for EdgeError {
// todo: create better enum representation. use this is placeholder
fn from(_value: SendError<Event>) -> Self {
EdgeError::TlsError
Copy link
Member

Choose a reason for hiding this comment

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

Might be weird seeing a TlsError here, but I'm guessing we're okay with this for now?

Copy link
Contributor Author

@thomasheartman thomasheartman Dec 12, 2024

Choose a reason for hiding this comment

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

Yes, I agree. Hence the comment. I just didn't want to start implementing a new error enum member when cleaning this up. We should handle this as part of testing, because I don't think it is the right error.

server/src/http/broadcaster.rs Outdated Show resolved Hide resolved
server/src/http/broadcaster.rs Show resolved Hide resolved
server/src/http/broadcaster.rs Outdated Show resolved Hide resolved
server/src/http/broadcaster.rs Outdated Show resolved Hide resolved
server/src/http/broadcaster.rs Outdated Show resolved Hide resolved
server/src/http/broadcaster.rs Show resolved Hide resolved
engine_cache: engines,
refresh_interval: features_refresh_interval,
persistence,
strict,
app_name: app_name.into(),
#[cfg(feature = "streaming")]
broadcaster: Broadcaster::new(features.clone()),
Copy link
Member

Choose a reason for hiding this comment

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

How about we move this above features_cache? That way we can clone here if needed, but leave features_cache: features intact as the owner of the features.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Smart 🧠

}
_ = metrics_pusher::prometheus_remote_write(prom_registry_for_write, edge.prometheus_remote_write_url, edge.prometheus_push_interval, edge.prometheus_username, edge.prometheus_password, app_name) => {
tracing::info!("Prometheus push unexpectedly shut down");
}
Copy link
Member

Choose a reason for hiding this comment

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

Wish we could simplify this and reduce the duplicated code but I don't have any better ideas atm.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I know. I hate this. But we can refactor it later. I don't think this'll be the final version (I hope not!).

@thomasheartman thomasheartman merged commit f511287 into main Dec 12, 2024
8 of 9 checks passed
@thomasheartman thomasheartman deleted the streaming-spike branch December 12, 2024 09:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

4 participants