mirror of
https://github.com/Cyrix126/gupaxx.git
synced 2024-12-22 22:59:27 +00:00
feat: replace unstable http client hyper to reqwest using stable hyper
This commit is contained in:
parent
29821566da
commit
17043bfe38
6 changed files with 316 additions and 244 deletions
199
Cargo.lock
generated
199
Cargo.lock
generated
|
@ -664,6 +664,12 @@ dependencies = [
|
|||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.6.0"
|
||||
|
@ -2078,8 +2084,6 @@ dependencies = [
|
|||
"env_logger",
|
||||
"figment",
|
||||
"flate2",
|
||||
"hyper",
|
||||
"hyper-tls",
|
||||
"image 0.25.0",
|
||||
"is_elevated",
|
||||
"log",
|
||||
|
@ -2090,6 +2094,7 @@ dependencies = [
|
|||
"rand",
|
||||
"readable",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"rfd",
|
||||
"serde",
|
||||
"serde-this-or-that",
|
||||
|
@ -2187,9 +2192,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.12"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
|
||||
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
|
@ -2198,12 +2203,24 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.6"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
|
||||
checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
|
@ -2213,12 +2230,6 @@ version = "1.8.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
|
@ -2227,38 +2238,57 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
|||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.28"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
|
||||
checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.6",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.6",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2416,6 +2446,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
|
||||
|
||||
[[package]]
|
||||
name = "is_elevated"
|
||||
version = "0.1.2"
|
||||
|
@ -3248,6 +3284,26 @@ version = "2.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.13"
|
||||
|
@ -3345,7 +3401,7 @@ dependencies = [
|
|||
"shared_library",
|
||||
"shell-words",
|
||||
"winapi",
|
||||
"winreg",
|
||||
"winreg 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3639,6 +3695,45 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d66674f2b6fb864665eea7a3c1ac4e3dfacd2fda83cf6f935a612e01b0e3338"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-tls",
|
||||
"hyper-util",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"winreg 0.50.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.14.1"
|
||||
|
@ -3719,6 +3814,15 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
|
||||
dependencies = [
|
||||
"base64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.14"
|
||||
|
@ -3869,6 +3973,18 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial"
|
||||
version = "0.4.0"
|
||||
|
@ -4151,6 +4267,12 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.30.7"
|
||||
|
@ -4420,6 +4542,28 @@ dependencies = [
|
|||
"winnow 0.6.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
|
@ -4432,6 +4576,7 @@ version = "0.1.40"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
|
@ -5376,6 +5521,16 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.50.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winres"
|
||||
version = "0.1.12"
|
||||
|
|
|
@ -57,8 +57,7 @@ egui_extras = { version = "0.27.0", features = ["image"] }
|
|||
#--------------------------------------------------------------------------------
|
||||
env_logger = "0.11.3"
|
||||
figment = { version = "0.10.15", features = ["toml"] }
|
||||
hyper = {version="0.14.28", features=["client", "http1"]}
|
||||
hyper-tls = "0.5.0"
|
||||
reqwest = {version = "0.12.2", default-features=false, features=["json", "default-tls"]}
|
||||
image = { version = "0.25.0", features = ["png"] }
|
||||
log = "0.4.21"
|
||||
num-format = { version = "0.4.4", default-features = false }
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
use crate::components::update::Pkg;
|
||||
use crate::{constants::*, macros::*};
|
||||
use egui::Color32;
|
||||
use hyper::{client::HttpConnector, Body, Client, Request};
|
||||
use log::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use reqwest::{Client, RequestBuilder};
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
@ -381,7 +381,7 @@ impl Ping {
|
|||
// Create HTTP client
|
||||
let info = "Creating HTTP Client".to_string();
|
||||
lock!(ping).msg = info;
|
||||
let client: Client<HttpConnector> = Client::builder().build(HttpConnector::new());
|
||||
let client = Client::new();
|
||||
|
||||
// Random User Agent
|
||||
let rand_user_agent = Pkg::get_user_agent();
|
||||
|
@ -393,16 +393,13 @@ impl Ping {
|
|||
let client = client.clone();
|
||||
let ping = Arc::clone(&ping);
|
||||
let node_vec = Arc::clone(&node_vec);
|
||||
let request = Request::builder()
|
||||
.method("POST")
|
||||
.uri("http://".to_string() + ip + ":" + rpc + "/json_rpc")
|
||||
let request = client
|
||||
.post("http://".to_string() + ip + ":" + rpc + "/json_rpc")
|
||||
.header("User-Agent", rand_user_agent)
|
||||
.body(hyper::Body::from(
|
||||
r#"{"jsonrpc":"2.0","id":"0","method":"get_info"}"#,
|
||||
))
|
||||
.unwrap();
|
||||
.body(r#"{"jsonrpc":"2.0","id":"0","method":"get_info"}"#);
|
||||
|
||||
let handle = tokio::task::spawn(async move {
|
||||
Self::response(client, request, ip, ping, percent, node_vec).await;
|
||||
Self::response(request, ip, ping, percent, node_vec).await;
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
@ -428,8 +425,7 @@ impl Ping {
|
|||
#[cold]
|
||||
#[inline(never)]
|
||||
async fn response(
|
||||
client: Client<HttpConnector>,
|
||||
request: Request<Body>,
|
||||
request: RequestBuilder,
|
||||
ip: &'static str,
|
||||
ping: Arc<Mutex<Self>>,
|
||||
percent: f32,
|
||||
|
@ -438,10 +434,10 @@ impl Ping {
|
|||
let ms;
|
||||
let now = Instant::now();
|
||||
|
||||
match tokio::time::timeout(Duration::from_secs(5), client.request(request)).await {
|
||||
match tokio::time::timeout(Duration::from_secs(5), request.send()).await {
|
||||
Ok(Ok(json_rpc)) => {
|
||||
// Attempt to convert to JSON-RPC.
|
||||
match hyper::body::to_bytes(json_rpc.into_body()).await {
|
||||
match json_rpc.bytes().await {
|
||||
Ok(b) => match serde_json::from_slice::<GetInfo<'_>>(&b) {
|
||||
Ok(rpc) => {
|
||||
if rpc.result.mainnet && rpc.result.synchronized {
|
||||
|
@ -485,6 +481,8 @@ impl Ping {
|
|||
//---------------------------------------------------------------------------------------------------- NODE
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use reqwest::Client;
|
||||
|
||||
use crate::components::node::{
|
||||
format_ip, REMOTE_NODES, REMOTE_NODE_LENGTH, REMOTE_NODE_MAX_CHARS,
|
||||
};
|
||||
|
@ -514,7 +512,6 @@ mod test {
|
|||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn full_ping() {
|
||||
use hyper::{client::HttpConnector, Client, Request};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
|
@ -524,7 +521,7 @@ mod test {
|
|||
}
|
||||
|
||||
// Create HTTP client
|
||||
let client: Client<HttpConnector> = Client::builder().build(HttpConnector::new());
|
||||
let client = Client::new();
|
||||
|
||||
// Random User Agent
|
||||
let rand_user_agent = Pkg::get_user_agent();
|
||||
|
@ -541,16 +538,13 @@ mod test {
|
|||
let client = client.clone();
|
||||
// Try 3 times before failure
|
||||
let mut i = 1;
|
||||
let mut response = loop {
|
||||
let request = Request::builder()
|
||||
.method("POST")
|
||||
.uri("http://".to_string() + ip + ":" + rpc + "/json_rpc")
|
||||
let response = loop {
|
||||
let request = client
|
||||
.post("http://".to_string() + ip + ":" + rpc + "/json_rpc")
|
||||
.header("User-Agent", rand_user_agent)
|
||||
.body(hyper::Body::from(
|
||||
r#"{"jsonrpc":"2.0","id":"0","method":"get_info"}"#,
|
||||
))
|
||||
.unwrap();
|
||||
match client.request(request).await {
|
||||
.body(r#"{"jsonrpc":"2.0","id":"0","method":"get_info"}"#);
|
||||
|
||||
match request.send().await {
|
||||
Ok(response) => break response,
|
||||
Err(e) => {
|
||||
println!("{:#?}", e);
|
||||
|
@ -565,8 +559,7 @@ mod test {
|
|||
}
|
||||
}
|
||||
};
|
||||
let body = hyper::body::to_bytes(response.body_mut()).await.unwrap();
|
||||
let getinfo: GetInfo = serde_json::from_slice(&body).unwrap();
|
||||
let getinfo = response.json::<GetInfo>().await.unwrap();
|
||||
assert!(getinfo.id == "0");
|
||||
assert!(getinfo.jsonrpc == "2.0");
|
||||
n += 1;
|
||||
|
|
|
@ -37,13 +37,12 @@ use crate::{
|
|||
utils::errors::{ErrorButtons, ErrorFerris, ErrorState},
|
||||
};
|
||||
use anyhow::{anyhow, Error};
|
||||
use hyper::{
|
||||
header::{HeaderValue, LOCATION},
|
||||
Body, Client, Request,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use log::*;
|
||||
use rand::distributions::Alphanumeric;
|
||||
use rand::{thread_rng, Rng};
|
||||
use reqwest::header::{LOCATION, USER_AGENT};
|
||||
use reqwest::{Client, RequestBuilder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
@ -322,8 +321,8 @@ impl Update {
|
|||
Ok(tmp_dir)
|
||||
}
|
||||
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
// #[cold]
|
||||
// #[inline(never)]
|
||||
// Get an HTTPS client. Uses [Arti] if Tor is enabled.
|
||||
// The base type looks something like [hyper::Client<...>].
|
||||
// This is then wrapped with the custom [ClientEnum] type to implement
|
||||
|
@ -337,13 +336,6 @@ impl Update {
|
|||
// ClientEnum::Tor(T) => get_response(... T ...)
|
||||
// ClientEnum::Https(H) => get_response(... H ...)
|
||||
//
|
||||
pub fn get_client() -> Result<ClientEnum, anyhow::Error> {
|
||||
let mut connector = hyper_tls::HttpsConnector::new();
|
||||
connector.https_only(true);
|
||||
let client = ClientEnum::Https(Client::builder().build(connector));
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
// Intermediate function that spawns a new thread
|
||||
|
@ -553,7 +545,7 @@ impl Update {
|
|||
info!("Update | {}", msg);
|
||||
*lock!(lock.msg) = msg;
|
||||
drop(lock);
|
||||
let client = Self::get_client()?;
|
||||
let client = Client::new();
|
||||
*lock2!(update, prog) += 5.0;
|
||||
info!("Update | Init ... OK ... {}%", lock2!(update, prog));
|
||||
|
||||
|
@ -584,11 +576,7 @@ impl Update {
|
|||
let link = pkg.link_metadata.to_string();
|
||||
// Send to async
|
||||
let handle: JoinHandle<Result<(), anyhow::Error>> = tokio::spawn(async move {
|
||||
match client {
|
||||
ClientEnum::Https(h) => {
|
||||
Pkg::get_metadata(new_ver, h, link, user_agent).await
|
||||
}
|
||||
}
|
||||
Pkg::get_metadata(new_ver, &client, link, user_agent).await
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
@ -726,11 +714,10 @@ impl Update {
|
|||
}
|
||||
};
|
||||
info!("Update | {} ... {}", pkg.name, link);
|
||||
let handle: JoinHandle<Result<(), anyhow::Error>> = tokio::spawn(async move {
|
||||
match client {
|
||||
ClientEnum::Https(h) => Pkg::get_bytes(bytes, h, link, user_agent).await,
|
||||
}
|
||||
});
|
||||
let handle: JoinHandle<Result<(), anyhow::Error>> =
|
||||
tokio::spawn(
|
||||
async move { Pkg::get_bytes(bytes, &client, link, user_agent).await },
|
||||
);
|
||||
handles.push(handle);
|
||||
}
|
||||
// Handle await
|
||||
|
@ -906,11 +893,6 @@ impl Update {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ClientEnum {
|
||||
Https(hyper::Client<hyper_tls::HttpsConnector<hyper::client::HttpConnector>>),
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Pkg struct/impl
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pkg {
|
||||
|
@ -919,7 +901,7 @@ pub struct Pkg {
|
|||
link_prefix: &'static str,
|
||||
link_suffix: &'static str,
|
||||
link_extension: &'static str,
|
||||
bytes: Arc<Mutex<hyper::body::Bytes>>,
|
||||
bytes: Arc<Mutex<Bytes>>,
|
||||
new_ver: Arc<Mutex<String>>,
|
||||
}
|
||||
|
||||
|
@ -974,35 +956,28 @@ impl Pkg {
|
|||
#[cold]
|
||||
#[inline(never)]
|
||||
// Generate GET request based off input URI + fake user agent
|
||||
fn get_request(link: String, user_agent: &'static str) -> Result<Request<Body>, anyhow::Error> {
|
||||
let request = Request::builder()
|
||||
.method("GET")
|
||||
.uri(link)
|
||||
.header(
|
||||
hyper::header::USER_AGENT,
|
||||
HeaderValue::from_static(user_agent),
|
||||
)
|
||||
.body(Body::empty())?;
|
||||
Ok(request)
|
||||
fn get_request(
|
||||
client: &Client,
|
||||
link: String,
|
||||
user_agent: &'static str,
|
||||
) -> Result<RequestBuilder, anyhow::Error> {
|
||||
Ok(client.get(link).header(USER_AGENT, user_agent))
|
||||
}
|
||||
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
// Get metadata using [Generic hyper::client<C>] & [Request]
|
||||
// and change [version, prog] under an Arc<Mutex>
|
||||
async fn get_metadata<C>(
|
||||
async fn get_metadata(
|
||||
new_ver: Arc<Mutex<String>>,
|
||||
client: Client<C>,
|
||||
client: &Client,
|
||||
link: String,
|
||||
user_agent: &'static str,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
C: hyper::client::connect::Connect + Clone + Send + Sync + 'static,
|
||||
{
|
||||
let request = Pkg::get_request(link, user_agent)?;
|
||||
let mut response = client.request(request).await?;
|
||||
let body = hyper::body::to_bytes(response.body_mut()).await?;
|
||||
let body: TagName = serde_json::from_slice(&body)?;
|
||||
) -> Result<(), Error> {
|
||||
let request = Pkg::get_request(&client, link, user_agent)?;
|
||||
let response = request.send().await?;
|
||||
dbg!(&response);
|
||||
let body = response.json::<TagName>().await?;
|
||||
*lock!(new_ver) = body.tag_name;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1011,22 +986,20 @@ impl Pkg {
|
|||
#[inline(never)]
|
||||
// Takes a [Request], fills the appropriate [Pkg]
|
||||
// [bytes] field with the [Archive/Standalone]
|
||||
async fn get_bytes<C>(
|
||||
async fn get_bytes(
|
||||
bytes: Arc<Mutex<bytes::Bytes>>,
|
||||
client: Client<C>,
|
||||
client: &Client,
|
||||
link: String,
|
||||
user_agent: &'static str,
|
||||
) -> Result<(), anyhow::Error>
|
||||
where
|
||||
C: hyper::client::connect::Connect + Clone + Send + Sync + 'static,
|
||||
{
|
||||
let request = Self::get_request(link, user_agent)?;
|
||||
let mut response = client.request(request).await?;
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let request = Self::get_request(&client, link, user_agent)?;
|
||||
let mut response = request.send().await?;
|
||||
// GitHub sends a 302 redirect, so we must follow
|
||||
// the [Location] header... only if Reqwest had custom
|
||||
// connectors so I didn't have to manually do this...
|
||||
if response.headers().contains_key(LOCATION) {
|
||||
let request = Self::get_request(
|
||||
response = Self::get_request(
|
||||
&client,
|
||||
response
|
||||
.headers()
|
||||
.get(LOCATION)
|
||||
|
@ -1034,10 +1007,11 @@ impl Pkg {
|
|||
.to_str()?
|
||||
.to_string(),
|
||||
user_agent,
|
||||
)?;
|
||||
response = client.request(request).await?;
|
||||
)?
|
||||
.send()
|
||||
.await?;
|
||||
}
|
||||
let body = hyper::body::to_bytes(response.into_body()).await?;
|
||||
let body = response.bytes().await?;
|
||||
*lock!(bytes) = body;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ use anyhow::{anyhow, Result};
|
|||
use log::*;
|
||||
use readable::num::Unsigned;
|
||||
use readable::up::Uptime;
|
||||
use reqwest::header::AUTHORIZATION;
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::path::Path;
|
||||
|
@ -444,8 +446,7 @@ impl Helper {
|
|||
let output_parse = Arc::clone(&lock!(process).output_parse);
|
||||
let output_pub = Arc::clone(&lock!(process).output_pub);
|
||||
|
||||
let client: hyper::Client<hyper::client::HttpConnector> =
|
||||
hyper::Client::builder().build(hyper::client::HttpConnector::new());
|
||||
let client = Client::new();
|
||||
let start = lock!(process).start;
|
||||
let api_uri = {
|
||||
if !api_ip_port.ends_with('/') {
|
||||
|
@ -617,7 +618,7 @@ impl Helper {
|
|||
);
|
||||
// Send an HTTP API request
|
||||
debug!("XMRig Watchdog | Attempting HTTP API request...");
|
||||
match PrivXmrigApi::request_xmrig_api(client.clone(), &api_uri, token).await {
|
||||
match PrivXmrigApi::request_xmrig_api(&client, &api_uri, token).await {
|
||||
Ok(priv_api) => {
|
||||
debug!("XMRig Watchdog | HTTP API request OK, attempting [update_from_priv()]");
|
||||
PubXmrigApi::update_from_priv(&pub_api, priv_api);
|
||||
|
@ -809,27 +810,22 @@ impl PrivXmrigApi {
|
|||
#[inline]
|
||||
// Send an HTTP request to XMRig's API, serialize it into [Self] and return it
|
||||
async fn request_xmrig_api(
|
||||
client: hyper::Client<hyper::client::HttpConnector>,
|
||||
client: &Client,
|
||||
api_uri: &str,
|
||||
token: &str,
|
||||
) -> std::result::Result<Self, anyhow::Error> {
|
||||
let request = hyper::Request::builder()
|
||||
.method("GET")
|
||||
.header("Authorization", ["Bearer ", token].concat())
|
||||
.uri(api_uri)
|
||||
.body(hyper::Body::empty())?;
|
||||
let response = tokio::time::timeout(
|
||||
std::time::Duration::from_millis(5000),
|
||||
client.request(request),
|
||||
)
|
||||
.await?;
|
||||
let body = hyper::body::to_bytes(response?.body_mut()).await?;
|
||||
Ok(serde_json::from_slice::<Self>(&body)?)
|
||||
let request = client
|
||||
.get(api_uri)
|
||||
.header(AUTHORIZATION, ["Bearer ", token].concat());
|
||||
Ok(request
|
||||
.timeout(std::time::Duration::from_millis(5000))
|
||||
.send()
|
||||
.await?.json().await?)
|
||||
}
|
||||
#[inline]
|
||||
// // Replace config with new node
|
||||
pub async fn update_xmrig_config(
|
||||
client: &hyper::Client<hyper::client::HttpConnector>,
|
||||
client: &Client,
|
||||
api_uri: &str,
|
||||
token: &str,
|
||||
node: &XvbNode,
|
||||
|
@ -837,19 +833,10 @@ impl PrivXmrigApi {
|
|||
gui_api_xmrig: &Arc<Mutex<PubXmrigApi>>,
|
||||
) -> Result<()> {
|
||||
// get config
|
||||
let request = hyper::Request::builder()
|
||||
.method("GET")
|
||||
.header("Authorization", ["Bearer ", token].concat())
|
||||
.uri(api_uri)
|
||||
.body(hyper::Body::empty())?;
|
||||
let response = tokio::time::timeout(
|
||||
std::time::Duration::from_millis(500),
|
||||
client.request(request),
|
||||
)
|
||||
.await?;
|
||||
let body = hyper::body::to_bytes(response?.body_mut()).await?;
|
||||
// deserialize to json
|
||||
let mut config = serde_json::from_slice::<Value>(&body)?;
|
||||
let request = client
|
||||
.get(api_uri)
|
||||
.header(AUTHORIZATION, ["Bearer ", token].concat());
|
||||
let mut config = request.send().await?.json::<Value>().await?;
|
||||
// modify node configuration
|
||||
let uri = [node.url(), ":".to_string(), node.port()].concat();
|
||||
info!("replace xmrig config with node {}", uri);
|
||||
|
@ -868,20 +855,15 @@ impl PrivXmrigApi {
|
|||
.pointer_mut("/pools/0/keepalive")
|
||||
.ok_or_else(|| anyhow!("pools/0/keepalive does not exist in xmrig config"))? =
|
||||
node.keepalive().into();
|
||||
// reconstruct body from new config
|
||||
let body = hyper::body::Body::from(config.to_string());
|
||||
// send new config
|
||||
let request = hyper::Request::builder()
|
||||
.method("PUT")
|
||||
client
|
||||
.put(api_uri)
|
||||
.header("Authorization", ["Bearer ", token].concat())
|
||||
.header("Content-Type", "application/json")
|
||||
.uri(api_uri)
|
||||
.body(body)?;
|
||||
tokio::time::timeout(
|
||||
std::time::Duration::from_millis(500),
|
||||
client.request(request),
|
||||
)
|
||||
.await??;
|
||||
.timeout(std::time::Duration::from_secs(5))
|
||||
.body(config.to_string())
|
||||
.send()
|
||||
.await?;
|
||||
// update process status
|
||||
lock!(gui_api_xmrig).node = node.to_string();
|
||||
anyhow::Ok(())
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
use anyhow::{bail, Result};
|
||||
use bytes::Bytes;
|
||||
use derive_more::Display;
|
||||
use hyper::client::HttpConnector;
|
||||
use hyper::{Client, Request, StatusCode};
|
||||
use hyper_tls::HttpsConnector;
|
||||
use log::{debug, error, info, warn};
|
||||
use readable::num::Float;
|
||||
use readable::up::Uptime;
|
||||
use reqwest::{Client, StatusCode};
|
||||
use serde::Deserialize;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Write;
|
||||
|
@ -137,14 +134,10 @@ impl Helper {
|
|||
process_xmrig: Arc<Mutex<Process>>,
|
||||
) {
|
||||
// client for http and one for https, both will be valid for the thread scope.
|
||||
let https = HttpsConnector::new();
|
||||
let client_https = hyper::Client::builder().build(https);
|
||||
// client http should be created only if process completely started, because it's not useful otherwise, but it would possibly be unitiliazed.
|
||||
let client_http =
|
||||
Arc::new(hyper::Client::builder().build(hyper::client::HttpConnector::new()));
|
||||
let client = reqwest::Client::new();
|
||||
info!("XvB | verify address and token");
|
||||
if let Err(err) =
|
||||
XvbPrivStats::request_api(&client_https, &state_p2pool.address, &state_xvb.token).await
|
||||
XvbPrivStats::request_api(&client, &state_p2pool.address, &state_xvb.token).await
|
||||
{
|
||||
// send to console: token non existent for address on XvB server
|
||||
warn!("Xvb | Start ... Partially failed because token and associated address are not existent on XvB server: {}\n", err);
|
||||
|
@ -183,13 +176,12 @@ impl Helper {
|
|||
lock!(process).state = ProcessState::Alive;
|
||||
|
||||
let pub_api_c = pub_api.clone();
|
||||
let client_http_c = client_http.clone();
|
||||
let client = client.clone();
|
||||
let process_c = process.clone();
|
||||
let gui_api_c = gui_api.clone();
|
||||
// will check which pool to use, will send NotMining if
|
||||
spawn(async move {
|
||||
XvbNode::update_fastest_node(&client_http_c, &pub_api_c, &gui_api_c, &process_c)
|
||||
.await;
|
||||
XvbNode::update_fastest_node(&client, &pub_api_c, &gui_api_c, &process_c).await;
|
||||
});
|
||||
}
|
||||
// let mut old_shares = 0;
|
||||
|
@ -240,7 +232,7 @@ impl Helper {
|
|||
if signal_interrupt(
|
||||
process.clone(),
|
||||
start.into(),
|
||||
&client_http,
|
||||
&client,
|
||||
&pub_api,
|
||||
&gui_api,
|
||||
&gui_api_xmrig,
|
||||
|
@ -255,14 +247,14 @@ impl Helper {
|
|||
// do not wait for the request to finish so that they are retrieved at exactly one minute interval.
|
||||
// Private API will also use this instant if XvB is Alive.
|
||||
last_request = tokio::time::Instant::now();
|
||||
let client_https_c = client_https.clone();
|
||||
let client_c = client.clone();
|
||||
let pub_api_c = pub_api.clone();
|
||||
let gui_api_c = gui_api.clone();
|
||||
let process_c = process.clone();
|
||||
// will send a stop signal if it failed or update data with new one.
|
||||
spawn(async move {
|
||||
debug!("XvB Watchdog | Attempting HTTP public API request...");
|
||||
match XvbPubStats::request_api(&client_https_c).await {
|
||||
match XvbPubStats::request_api(&client_c).await {
|
||||
Ok(new_data) => {
|
||||
debug!("XvB Watchdog | HTTP API request OK");
|
||||
lock!(&pub_api_c).stats_pub = new_data;
|
||||
|
@ -289,7 +281,7 @@ impl Helper {
|
|||
if lock!(process).state == ProcessState::Alive {
|
||||
debug!("XvB Watchdog | Attempting HTTP private API request...");
|
||||
// reload private stats, send signal if error
|
||||
let client_https_c = client_https.clone();
|
||||
let client_https_c = client.clone();
|
||||
let pub_api_c = pub_api.clone();
|
||||
let gui_api_c = gui_api.clone();
|
||||
let process_c = process.clone();
|
||||
|
@ -297,21 +289,9 @@ impl Helper {
|
|||
let token = state_xvb.token.clone();
|
||||
spawn(async move {
|
||||
match XvbPrivStats::request_api(&client_https_c, &address, &token).await {
|
||||
Ok(b) => {
|
||||
Ok(new_data) => {
|
||||
debug!("XvB Watchdog | HTTP API request OK");
|
||||
let new_data = match serde_json::from_slice::<XvbPrivStats>(&b) {
|
||||
Ok(data) => Some(data),
|
||||
Err(e) => {
|
||||
warn!("XvB Watchdog | Data provided from private API is not deserializ-able.Error: {}", e);
|
||||
output_console(&gui_api_c, &format!("XvB Watchdog | Data provided from private API is not deserializ-able.Error: {}", e));
|
||||
lock!(process_c).state = ProcessState::Failed;
|
||||
lock!(process_c).signal = ProcessSignal::Stop;
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(new_data) = new_data {
|
||||
lock!(&pub_api_c).stats_priv = new_data;
|
||||
}
|
||||
lock!(&pub_api_c).stats_priv = new_data;
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
|
@ -358,7 +338,7 @@ impl Helper {
|
|||
// the time that takes the algorithm do decide the next ten minutes could means less p2pool mining. It is solved by the buffer and spawning requests.
|
||||
last_algorithm = tokio::time::Instant::now();
|
||||
// request XMrig to mine on P2pool
|
||||
let client_http_c = client_http.clone();
|
||||
let client_http_c = client.clone();
|
||||
let gui_api_c = gui_api.clone();
|
||||
let token_xmrig = Arc::new(state_xmrig.token.clone());
|
||||
let token_xmrig_c = token_xmrig.clone();
|
||||
|
@ -443,7 +423,7 @@ impl Helper {
|
|||
// sleep 10m less spared time then request XMrig to mine on XvB
|
||||
let was_instant = last_algorithm;
|
||||
let gui_api_c = gui_api.clone();
|
||||
let client_http_c = client_http.clone();
|
||||
let client_http_c = client.clone();
|
||||
let gui_api_xmrig_c = gui_api_xmrig.clone();
|
||||
spawn(async move {
|
||||
Helper::sleep_then_update_node_xmrig(
|
||||
|
@ -541,7 +521,7 @@ impl Helper {
|
|||
async fn sleep_then_update_node_xmrig(
|
||||
was_instant: &tokio::time::Instant,
|
||||
spared_time: u32,
|
||||
client: &Client<HttpConnector>,
|
||||
client: &Client,
|
||||
api_uri: &str,
|
||||
token_xmrig: &str,
|
||||
address: &str,
|
||||
|
@ -651,50 +631,45 @@ pub struct XvbPrivStats {
|
|||
impl XvbPubStats {
|
||||
#[inline]
|
||||
// Send an HTTP request to XvB's API, serialize it into [Self] and return it
|
||||
async fn request_api(
|
||||
client: &hyper::Client<HttpsConnector<HttpConnector>>,
|
||||
) -> std::result::Result<Self, anyhow::Error> {
|
||||
let request = hyper::Request::builder()
|
||||
.method("GET")
|
||||
.uri(XVB_URL_PUBLIC_API)
|
||||
.body(hyper::Body::empty())?;
|
||||
let response =
|
||||
tokio::time::timeout(std::time::Duration::from_secs(10), client.request(request))
|
||||
.await?;
|
||||
// let response = client.request(request).await;
|
||||
|
||||
let body = hyper::body::to_bytes(response?.body_mut()).await?;
|
||||
Ok(serde_json::from_slice::<Self>(&body)?)
|
||||
async fn request_api(client: &Client) -> std::result::Result<Self, anyhow::Error> {
|
||||
Ok(client
|
||||
.get(XVB_URL_PUBLIC_API)
|
||||
.send()
|
||||
.await?
|
||||
.json::<Self>()
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
impl XvbPrivStats {
|
||||
pub async fn request_api(
|
||||
client: &hyper::Client<HttpsConnector<HttpConnector>>,
|
||||
address: &str,
|
||||
token: &str,
|
||||
) -> Result<Bytes> {
|
||||
if let Ok(request) = hyper::Request::builder()
|
||||
.method("GET")
|
||||
.uri(format!(
|
||||
"{}/cgi-bin/p2pool_bonus_history_api.cgi?address={}&token={}",
|
||||
XVB_URL, address, token
|
||||
))
|
||||
.body(hyper::Body::empty())
|
||||
{
|
||||
match client.request(request).await {
|
||||
Ok(mut resp) => match resp.status() {
|
||||
StatusCode::OK => Ok(hyper::body::to_bytes(resp.body_mut()).await?),
|
||||
StatusCode::UNPROCESSABLE_ENTITY => {
|
||||
bail!("the token is invalid for this xmr address.")
|
||||
}
|
||||
_ => bail!("The status of the response is not expected"),
|
||||
},
|
||||
pub async fn request_api(client: &Client, address: &str, token: &str) -> Result<Self> {
|
||||
let resp = client
|
||||
.get(
|
||||
[
|
||||
XVB_URL,
|
||||
"/cgi-bin/p2pool_bonus_history_api.cgi?address=",
|
||||
address,
|
||||
"&token=",
|
||||
token,
|
||||
]
|
||||
.concat(),
|
||||
)
|
||||
.send()
|
||||
.await?;
|
||||
match resp.status() {
|
||||
StatusCode::OK => match resp.json::<Self>().await {
|
||||
Ok(s) => Ok(s),
|
||||
Err(err) => {
|
||||
bail!("error from response: {}", err)
|
||||
error!("XvB Watchdog | Data provided from private API is not deserializ-able.Error: {}", err);
|
||||
bail!(
|
||||
"Data provided from private API is not deserializ-able.Error: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
},
|
||||
StatusCode::UNPROCESSABLE_ENTITY => {
|
||||
bail!("the token is invalid for this xmr address.")
|
||||
}
|
||||
} else {
|
||||
bail!("request could not be build")
|
||||
_ => bail!("The status of the response is not expected"),
|
||||
}
|
||||
}
|
||||
fn calcul_donated_time(
|
||||
|
@ -837,7 +812,7 @@ impl XvbNode {
|
|||
}
|
||||
|
||||
pub async fn update_fastest_node(
|
||||
client: &Arc<Client<HttpConnector>>,
|
||||
client: &Client,
|
||||
pub_api_xvb: &Arc<Mutex<PubXvbApi>>,
|
||||
gui_api_xvb: &Arc<Mutex<PubXvbApi>>,
|
||||
process_xvb: &Arc<Mutex<Process>>,
|
||||
|
@ -893,20 +868,16 @@ impl XvbNode {
|
|||
}
|
||||
lock!(pub_api_xvb).stats_priv.node = node;
|
||||
}
|
||||
async fn ping(ip: &str, client: &Client<HttpConnector>) -> u128 {
|
||||
let request = Request::builder()
|
||||
.method("POST")
|
||||
.uri("http://".to_string() + ip + ":" + XVB_NODE_RPC + "/json_rpc")
|
||||
.body(hyper::Body::from(
|
||||
r#"{"jsonrpc":"2.0","id":"0","method":"get_info"}"#,
|
||||
))
|
||||
.expect("hyper request should build.");
|
||||
async fn ping(ip: &str, client: &Client) -> u128 {
|
||||
let request = client
|
||||
.post("http://".to_string() + ip + ":" + XVB_NODE_RPC + "/json_rpc")
|
||||
.body(r#"{"jsonrpc":"2.0","id":"0","method":"get_info"}"#);
|
||||
let ms;
|
||||
let now = Instant::now();
|
||||
match tokio::time::timeout(Duration::from_secs(8), client.request(request)).await {
|
||||
match tokio::time::timeout(Duration::from_secs(8), request.send()).await {
|
||||
Ok(Ok(json_rpc)) => {
|
||||
// Attempt to convert to JSON-RPC.
|
||||
match hyper::body::to_bytes(json_rpc.into_body()).await {
|
||||
match json_rpc.bytes().await {
|
||||
Ok(b) => match serde_json::from_slice::<GetInfo<'_>>(&b) {
|
||||
Ok(rpc) => {
|
||||
if rpc.result.mainnet && rpc.result.synchronized {
|
||||
|
@ -958,7 +929,7 @@ impl PubXvbApi {
|
|||
fn signal_interrupt(
|
||||
process: Arc<Mutex<Process>>,
|
||||
start: Instant,
|
||||
client_http: &Arc<Client<HttpConnector>>,
|
||||
client_http: &Client,
|
||||
pub_api: &Arc<Mutex<PubXvbApi>>,
|
||||
gui_api: &Arc<Mutex<PubXvbApi>>,
|
||||
gui_api_xmrig: &Arc<Mutex<PubXmrigApi>>,
|
||||
|
@ -1081,18 +1052,16 @@ mod test {
|
|||
};
|
||||
|
||||
use super::{PubXvbApi, XvbPrivStats, XvbPubStats};
|
||||
use hyper::Client;
|
||||
use hyper_tls::HttpsConnector;
|
||||
use reqwest::Client;
|
||||
|
||||
#[test]
|
||||
fn public_api_deserialize() {
|
||||
let https = HttpsConnector::new();
|
||||
let client = hyper::Client::builder().build(https);
|
||||
let new_data = thread::spawn(move || corr(client)).join().unwrap();
|
||||
let client = Client::new();
|
||||
let new_data = thread::spawn(move || corr(&client)).join().unwrap();
|
||||
assert!(!new_data.reward_yearly.is_empty());
|
||||
}
|
||||
#[tokio::main]
|
||||
async fn corr(client: Client<HttpsConnector<hyper::client::HttpConnector>>) -> XvbPubStats {
|
||||
async fn corr(client: &Client) -> XvbPubStats {
|
||||
XvbPubStats::request_api(&client).await.unwrap()
|
||||
}
|
||||
#[test]
|
||||
|
|
Loading…
Reference in a new issue