-
Notifications
You must be signed in to change notification settings - Fork 234
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
Explorer API - add port check and node description/stats proxy #731
Merged
Merged
Changes from 13 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
fbe2a5f
explorer-api: move mix node client operations into a package
mmsinclair 6a8e7a4
explorer-api: add port test for mixnodes with cache for results
mmsinclair fce51b6
explorer-api: add `humantime-serde` dependency
mmsinclair 0e16558
explorer-api: mix node API proxy
mmsinclair b246b8d
explorer-api: adjust naming
mmsinclair 094845a
explorer-api: fix up self refs
mmsinclair eddc323
explorer-api: add method to state to get a mix node by identity key
mmsinclair 5d3735a
explorer-api: add cached http resource to proxy the `/description` an…
mmsinclair 57add3f
explorer-api: set default mix node cache time to 30 minutes
mmsinclair 4cb614e
explorer-api: make clippy happy
mmsinclair 6225051
explorer-api: add CORS with wide open configuration
mmsinclair 7f88380
explorer-api: fixes from review feedback
mmsinclair 0c5cd8b
explorer-api: move port check test into separate function
mmsinclair 2e900f6
explorer-api: use `rocket-cors` that is pinned in the `validator-api`…
mmsinclair File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
use rocket::fairing::{Fairing, Info, Kind}; | ||
use rocket::http::Header; | ||
use rocket::{Request, Response}; | ||
|
||
// See https://github.com/SergioBenitez/Rocket/issues/25#issuecomment-838566038. | ||
// Tried to use `rocket_cors` however it requires lots of nightly builds, | ||
// so went for this instead to keep things simple | ||
|
||
pub(crate) struct Cors; | ||
|
||
#[rocket::async_trait] | ||
impl Fairing for Cors { | ||
fn info(&self) -> Info { | ||
Info { | ||
name: "Add CORS headers to responses", | ||
kind: Kind::Response, | ||
} | ||
} | ||
|
||
async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) { | ||
response.set_header(Header::new("Access-Control-Allow-Origin", "*")); | ||
response.set_header(Header::new("Access-Control-Allow-Methods", "GET, OPTIONS")); | ||
response.set_header(Header::new("Access-Control-Allow-Headers", "*")); | ||
response.set_header(Header::new("Access-Control-Allow-Credentials", "true")); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,37 @@ | ||
mod swagger; | ||
use log::info; | ||
use rocket_okapi::swagger_ui::make_swagger_ui; | ||
|
||
use crate::country_statistics::http::make_default_routes; | ||
use crate::country_statistics::http::country_statistics_make_default_routes; | ||
use crate::http::cors::Cors; | ||
use crate::http::swagger::get_docs; | ||
use crate::mix_node::http::mix_node_make_default_routes; | ||
use crate::ping::http::ping_make_default_routes; | ||
use crate::state::ExplorerApiStateContext; | ||
use log::info; | ||
use rocket_okapi::swagger_ui::make_swagger_ui; | ||
use rocket::Request; | ||
|
||
mod cors; | ||
mod swagger; | ||
|
||
pub(crate) fn start(state: ExplorerApiStateContext) { | ||
tokio::spawn(async move { | ||
info!("Starting up..."); | ||
|
||
let config = rocket::config::Config::release_default(); | ||
|
||
rocket::build() | ||
.configure(config) | ||
.mount("/countries", make_default_routes()) | ||
.mount("/countries", country_statistics_make_default_routes()) | ||
.mount("/ping", ping_make_default_routes()) | ||
.mount("/mix-node", mix_node_make_default_routes()) | ||
.mount("/swagger", make_swagger_ui(&get_docs())) | ||
// .register("/", catchers![not_found]) | ||
.register("/", catchers![not_found]) | ||
.manage(state) | ||
// .manage(descriptor) | ||
// .manage(node_stats_pointer) | ||
.attach(Cors) | ||
.launch() | ||
.await | ||
}); | ||
} | ||
|
||
#[catch(404)] | ||
pub(crate) fn not_found(req: &Request) -> String { | ||
format!("I couldn't find '{}'. Try something else?", req.uri()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,8 @@ use log::info; | |
|
||
mod country_statistics; | ||
mod http; | ||
mod mix_node; | ||
mod mix_nodes; | ||
mod ping; | ||
mod state; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
use std::collections::HashMap; | ||
use std::time::{Duration, SystemTime}; | ||
|
||
#[derive(Clone)] | ||
pub(crate) struct Cache<T: Clone> { | ||
inner: HashMap<String, CacheItem<T>>, | ||
} | ||
|
||
impl<T: Clone> Cache<T> { | ||
pub(crate) fn new() -> Self { | ||
Cache { | ||
inner: HashMap::new(), | ||
} | ||
} | ||
|
||
pub(crate) fn get(&self, identity_key: &str) -> Option<T> | ||
where | ||
T: Clone, | ||
{ | ||
self.inner | ||
.get(identity_key) | ||
.filter(|cache_item| cache_item.valid_until > SystemTime::now()) | ||
.map(|cache_item| cache_item.value.clone()) | ||
} | ||
|
||
pub(crate) fn set(&mut self, identity_key: &str, value: T) { | ||
self.inner.insert( | ||
identity_key.to_string(), | ||
CacheItem { | ||
valid_until: SystemTime::now() + Duration::from_secs(60 * 30), | ||
value, | ||
}, | ||
); | ||
} | ||
} | ||
|
||
#[derive(Clone)] | ||
pub(crate) struct CacheItem<T> { | ||
pub(crate) value: T, | ||
pub(crate) valid_until: std::time::SystemTime, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
use reqwest::Error as ReqwestError; | ||
|
||
use rocket::serde::json::Json; | ||
use rocket::{Route, State}; | ||
|
||
use crate::mix_node::models::{NodeDescription, NodeStats}; | ||
use crate::state::ExplorerApiStateContext; | ||
|
||
pub fn mix_node_make_default_routes() -> Vec<Route> { | ||
routes_with_openapi![get_description, get_stats] | ||
} | ||
|
||
#[openapi(tag = "mix_node")] | ||
#[get("/<pubkey>/description")] | ||
pub(crate) async fn get_description( | ||
pubkey: &str, | ||
state: &State<ExplorerApiStateContext>, | ||
) -> Option<Json<NodeDescription>> { | ||
match state | ||
.inner | ||
.mix_node_cache | ||
.clone() | ||
.get_description(pubkey) | ||
.await | ||
{ | ||
Some(cache_value) => { | ||
trace!("Returning cached value for {}", pubkey); | ||
Some(Json(cache_value)) | ||
} | ||
None => { | ||
trace!("No valid cache value for {}", pubkey); | ||
match state.inner.get_mix_node(pubkey).await { | ||
jstuczyn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Some(bond) => { | ||
match get_mix_node_description( | ||
&bond.mix_node.host, | ||
&bond.mix_node.http_api_port, | ||
) | ||
.await | ||
{ | ||
Ok(response) => { | ||
// cache the response and return as the HTTP response | ||
state | ||
.inner | ||
.mix_node_cache | ||
.set_description(pubkey, response.clone()) | ||
.await; | ||
Some(Json(response)) | ||
} | ||
Err(e) => { | ||
error!( | ||
"Unable to get description for {} on {}:{} -> {}", | ||
pubkey, bond.mix_node.host, bond.mix_node.http_api_port, e | ||
); | ||
Option::None | ||
} | ||
} | ||
} | ||
None => Option::None, | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[openapi(tag = "mix_node")] | ||
#[get("/<pubkey>/stats")] | ||
pub(crate) async fn get_stats( | ||
pubkey: &str, | ||
state: &State<ExplorerApiStateContext>, | ||
) -> Option<Json<NodeStats>> { | ||
match state.inner.mix_node_cache.get_node_stats(pubkey).await { | ||
Some(cache_value) => { | ||
trace!("Returning cached value for {}", pubkey); | ||
Some(Json(cache_value)) | ||
} | ||
None => { | ||
trace!("No valid cache value for {}", pubkey); | ||
match state.inner.get_mix_node(pubkey).await { | ||
Some(bond) => { | ||
match get_mix_node_stats(&bond.mix_node.host, &bond.mix_node.http_api_port) | ||
.await | ||
{ | ||
Ok(response) => { | ||
// cache the response and return as the HTTP response | ||
state | ||
.inner | ||
.mix_node_cache | ||
.set_node_stats(pubkey, response.clone()) | ||
.await; | ||
Some(Json(response)) | ||
} | ||
Err(e) => { | ||
error!( | ||
"Unable to get description for {} on {}:{} -> {}", | ||
pubkey, bond.mix_node.host, bond.mix_node.http_api_port, e | ||
); | ||
Option::None | ||
} | ||
} | ||
} | ||
None => Option::None, | ||
} | ||
} | ||
} | ||
} | ||
|
||
async fn get_mix_node_description(host: &str, port: &u16) -> Result<NodeDescription, ReqwestError> { | ||
mmsinclair marked this conversation as resolved.
Show resolved
Hide resolved
|
||
reqwest::get(format!("http://{}:{}/description", host, port)) | ||
.await? | ||
.json::<NodeDescription>() | ||
.await | ||
} | ||
|
||
async fn get_mix_node_stats(host: &str, port: &u16) -> Result<NodeStats, ReqwestError> { | ||
reqwest::get(format!("http://{}:{}/stats", host, port)) | ||
.await? | ||
.json::<NodeStats>() | ||
.await | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
mod cache; | ||
pub(crate) mod http; | ||
pub(crate) mod models; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Just a suggestion, perhaps there should be some way of removing stale entries from the cache? Right now everything will just stay there forever
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.
Yeah, it is a bit wasteful generally - although I'm hoping the memory requirements for thousands of nodes will still be megabytes. I can add a cleanup background task sometime if it becomes a problem.