mirror of
https://github.com/sadoyan/aralez.git
synced 2026-04-29 22:38:36 +08:00
Cargo cleanup, dependency merge
This commit is contained in:
1491
Cargo.lock
generated
1491
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
16
Cargo.toml
@@ -11,7 +11,7 @@ panic = "abort"
|
|||||||
strip = true
|
strip = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.49.0", features = ["full"] }
|
tokio = { version = "1.51.1", features = ["full"] }
|
||||||
pingora = { version = "0.8.0", features = ["lb", "openssl"] } # openssl, rustls, boringssl
|
pingora = { version = "0.8.0", features = ["lb", "openssl"] } # openssl, rustls, boringssl
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
dashmap = "7.0.0-rc2"
|
dashmap = "7.0.0-rc2"
|
||||||
@@ -20,34 +20,26 @@ pingora-proxy = "0.8.0"
|
|||||||
pingora-http = "0.8.0"
|
pingora-http = "0.8.0"
|
||||||
pingora-limits = "0.8.0"
|
pingora-limits = "0.8.0"
|
||||||
async-trait = "0.1.89"
|
async-trait = "0.1.89"
|
||||||
env_logger = "0.11.9"
|
env_logger = "0.11.10"
|
||||||
log = "0.4.29"
|
log = "0.4.29"
|
||||||
futures = "0.3.32"
|
futures = "0.3.32"
|
||||||
notify = "9.0.0-rc.2"
|
notify = "9.0.0-rc.2"
|
||||||
axum = { version = "0.8.8" }
|
axum = { version = "0.8.8" }
|
||||||
#axum-server = { version = "0.8.0" }
|
|
||||||
reqwest = { version = "0.13.2", features = ["json", "stream"] }
|
reqwest = { version = "0.13.2", features = ["json", "stream"] }
|
||||||
serde_yaml = "0.9.34-deprecated"
|
serde_yml = "0.0.12"
|
||||||
rand = "0.10.0"
|
rand = "0.10.0"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
#jsonwebtoken = { version = "10.3.0", features = ["aws_lc_rs"] }
|
|
||||||
#jsonwebtoken = { version = "10.3.0", default-features = false, features = ["use_pem"] }
|
|
||||||
jsonwebtoken = { version = "10.3.0", default-features = false, features = ["use_pem", "rust_crypto"] }
|
jsonwebtoken = { version = "10.3.0", default-features = false, features = ["use_pem", "rust_crypto"] }
|
||||||
tonic = "0.14.5"
|
tonic = "0.14.5"
|
||||||
sha2 = { version = "0.11.0-rc.5", default-features = false }
|
sha2 = { version = "0.11.0-rc.5", default-features = false }
|
||||||
base16ct = { version = "1.0.0", features = ["alloc"] }
|
base16ct = { version = "1.0.0", features = ["alloc"] }
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
arc-swap = "1.8.2"
|
arc-swap = "1.9.1"
|
||||||
mimalloc = { version = "0.1.48", default-features = false }
|
mimalloc = { version = "0.1.48", default-features = false }
|
||||||
prometheus = "0.14.0"
|
prometheus = "0.14.0"
|
||||||
lazy_static = "1.5.0"
|
|
||||||
x509-parser = "0.18.1"
|
x509-parser = "0.18.1"
|
||||||
rustls-pemfile = "2.2.0"
|
rustls-pemfile = "2.2.0"
|
||||||
tower-http = { version = "0.6.8", features = ["fs"] }
|
tower-http = { version = "0.6.8", features = ["fs"] }
|
||||||
once_cell = "1.21.3"
|
|
||||||
privdrop = "0.5.6"
|
privdrop = "0.5.6"
|
||||||
ctrlc = "3.5.2"
|
ctrlc = "3.5.2"
|
||||||
port_check = "0.3.0"
|
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
http = "1.4.0"
|
|
||||||
itoa = "1.0.14"
|
|
||||||
|
|||||||
@@ -119,18 +119,11 @@ async fn http_request(url: &str, method: &str, payload: &str, client: &Client) -
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn ping_grpc(addr: &str) -> bool {
|
pub async fn ping_grpc(addr: &str) -> bool {
|
||||||
let endpoint_result = Endpoint::from_shared(addr.to_owned());
|
let endpoint = match Endpoint::from_shared(addr.to_owned()) {
|
||||||
|
Ok(e) => e.timeout(Duration::from_secs(2)),
|
||||||
if let Ok(endpoint) = endpoint_result {
|
Err(_) => return false,
|
||||||
let endpoint = endpoint.timeout(Duration::from_secs(2));
|
};
|
||||||
|
tokio::time::timeout(Duration::from_secs(3), endpoint.connect()).await.ok().and_then(Result::ok).is_some()
|
||||||
match tokio::time::timeout(Duration::from_secs(3), endpoint.connect()).await {
|
|
||||||
Ok(Ok(_channel)) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn detect_tls(ip: &str, port: &u16, client: &Client) -> (bool, Option<Version>) {
|
async fn detect_tls(ip: &str, port: &u16, client: &Client) -> (bool, Option<Version>) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use http::method::Method;
|
use pingora_http::Method;
|
||||||
use http::StatusCode;
|
use pingora_http::StatusCode;
|
||||||
use pingora_http::Version;
|
use pingora_http::Version;
|
||||||
use prometheus::{register_histogram, register_int_counter, register_int_counter_vec, Histogram, IntCounter, IntCounterVec};
|
use prometheus::{register_histogram, register_int_counter, register_int_counter_vec, Histogram, IntCounter, IntCounterVec};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -12,6 +12,7 @@ pub struct MetricTypes {
|
|||||||
pub latency: Duration,
|
pub latency: Duration,
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub static ref REQUEST_COUNT: IntCounter = register_int_counter!(
|
pub static ref REQUEST_COUNT: IntCounter = register_int_counter!(
|
||||||
"aralez_requests_total",
|
"aralez_requests_total",
|
||||||
@@ -52,9 +53,47 @@ lazy_static::lazy_static! {
|
|||||||
"Total number of errors"
|
"Total number of errors"
|
||||||
).unwrap();
|
).unwrap();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
pub static REQUEST_COUNT: LazyLock<IntCounter> = LazyLock::new(|| register_int_counter!("aralez_requests_total", "Total number of requests handled by Aralez").unwrap());
|
||||||
|
|
||||||
|
pub static RESPONSE_CODES: LazyLock<IntCounterVec> =
|
||||||
|
LazyLock::new(|| register_int_counter_vec!("aralez_responses_total", "Responses grouped by status code", &["status"]).unwrap());
|
||||||
|
|
||||||
|
pub static REQUEST_LATENCY: LazyLock<Histogram> = LazyLock::new(|| {
|
||||||
|
register_histogram!(
|
||||||
|
"aralez_request_latency_seconds",
|
||||||
|
"Request latency in seconds",
|
||||||
|
vec![0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0]
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
pub static RESPONSE_LATENCY: LazyLock<Histogram> = LazyLock::new(|| {
|
||||||
|
register_histogram!(
|
||||||
|
"aralez_response_latency_seconds",
|
||||||
|
"Response latency in seconds",
|
||||||
|
vec![0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.0, 5.0]
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
pub static REQUESTS_BY_METHOD: LazyLock<IntCounterVec> =
|
||||||
|
LazyLock::new(|| register_int_counter_vec!("aralez_requests_by_method_total", "Number of requests by HTTP method", &["method"]).unwrap());
|
||||||
|
|
||||||
|
pub static REQUESTS_BY_UPSTREAM: LazyLock<IntCounterVec> =
|
||||||
|
LazyLock::new(|| register_int_counter_vec!("aralez_requests_by_upstream", "Number of requests by UPSTREAM server", &["upstream"]).unwrap());
|
||||||
|
|
||||||
|
pub static REQUESTS_BY_VERSION: LazyLock<IntCounterVec> =
|
||||||
|
LazyLock::new(|| register_int_counter_vec!("aralez_requests_by_version_total", "Number of requests by HTTP versions", &["version"]).unwrap());
|
||||||
|
|
||||||
|
pub static ERROR_COUNT: LazyLock<IntCounter> = LazyLock::new(|| register_int_counter!("aralez_errors_total", "Total number of errors").unwrap());
|
||||||
|
|
||||||
pub fn calc_metrics(metric_types: &MetricTypes) {
|
pub fn calc_metrics(metric_types: &MetricTypes) {
|
||||||
REQUEST_COUNT.inc();
|
REQUEST_COUNT.inc();
|
||||||
|
ERROR_COUNT.inc();
|
||||||
let timer = REQUEST_LATENCY.start_timer();
|
let timer = REQUEST_LATENCY.start_timer();
|
||||||
timer.observe_duration();
|
timer.observe_duration();
|
||||||
|
|
||||||
@@ -66,7 +105,7 @@ pub fn calc_metrics(metric_types: &MetricTypes) {
|
|||||||
_ => "Unknown",
|
_ => "Unknown",
|
||||||
};
|
};
|
||||||
REQUESTS_BY_VERSION.with_label_values(&[&version_str]).inc();
|
REQUESTS_BY_VERSION.with_label_values(&[&version_str]).inc();
|
||||||
RESPONSE_CODES.with_label_values(&[metric_types.code.unwrap_or(http::StatusCode::GONE).as_str()]).inc();
|
RESPONSE_CODES.with_label_values(&[metric_types.code.unwrap_or(StatusCode::GONE).as_str()]).inc();
|
||||||
REQUESTS_BY_METHOD.with_label_values(&[&metric_types.method]).inc();
|
REQUESTS_BY_METHOD.with_label_values(&[&metric_types.method]).inc();
|
||||||
REQUESTS_BY_UPSTREAM.with_label_values(&[metric_types.upstream.as_ref()]).inc();
|
REQUESTS_BY_UPSTREAM.with_label_values(&[metric_types.upstream.as_ref()]).inc();
|
||||||
RESPONSE_LATENCY.observe(metric_types.latency.as_secs_f64());
|
RESPONSE_LATENCY.observe(metric_types.latency.as_secs_f64());
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ pub async fn load_configuration(d: &str, kind: &str) -> (Option<Configuration>,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut parsed: Config = match serde_yaml::from_str(&yaml_data) {
|
let mut parsed: Config = match serde_yml::from_str(&yaml_data) {
|
||||||
Ok(cfg) => cfg,
|
Ok(cfg) => cfg,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to parse upstreams file: {}", e);
|
error!("Failed to parse upstreams file: {}", e);
|
||||||
@@ -67,7 +67,7 @@ pub async fn load_configuration(d: &str, kind: &str) -> (Option<Configuration>,
|
|||||||
|
|
||||||
if let Some(ref mut upstreams) = parsed.upstreams {
|
if let Some(ref mut upstreams) = parsed.upstreams {
|
||||||
for uconf in conf_files {
|
for uconf in conf_files {
|
||||||
let p: HashMap<String, HostConfig> = match serde_yaml::from_str(&uconf) {
|
let p: HashMap<String, HostConfig> = match serde_yml::from_str(&uconf) {
|
||||||
Ok(ucfg) => ucfg,
|
Ok(ucfg) => ucfg,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to parse upstreams file: {}", e);
|
error!("Failed to parse upstreams file: {}", e);
|
||||||
@@ -209,8 +209,8 @@ async fn populate_file_upstreams(config: &mut Configuration, parsed: &Config) {
|
|||||||
pub fn parce_main_config(path: &str) -> AppConfig {
|
pub fn parce_main_config(path: &str) -> AppConfig {
|
||||||
let data = fs::read_to_string(path).unwrap();
|
let data = fs::read_to_string(path).unwrap();
|
||||||
let reply = DashMap::new();
|
let reply = DashMap::new();
|
||||||
let cfg: HashMap<String, String> = serde_yaml::from_str(&*data).expect("Failed to parse main config file");
|
let cfg: HashMap<String, String> = serde_yml::from_str(&*data).expect("Failed to parse main config file");
|
||||||
let mut cfo: AppConfig = serde_yaml::from_str(&*data).expect("Failed to parse main config file");
|
let mut cfo: AppConfig = serde_yml::from_str(&*data).expect("Failed to parse main config file");
|
||||||
log_builder(&cfo);
|
log_builder(&cfo);
|
||||||
cfo.hc_method = cfo.hc_method.to_uppercase();
|
cfo.hc_method = cfo.hc_method.to_uppercase();
|
||||||
for (k, v) in cfg {
|
for (k, v) in cfg {
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
use once_cell::sync::Lazy;
|
use std::sync::{LazyLock, RwLock};
|
||||||
use std::sync::RwLock;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SharedState {
|
pub struct SharedState {
|
||||||
pub first_run: bool,
|
pub first_run: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static GLOBAL_STATE: Lazy<RwLock<SharedState>> = Lazy::new(|| RwLock::new(SharedState { first_run: true }));
|
pub static GLOBAL_STATE: LazyLock<RwLock<SharedState>> = LazyLock::new(|| RwLock::new(SharedState { first_run: true }));
|
||||||
|
|
||||||
pub fn mark_not_first_run() {
|
pub fn mark_not_first_run() {
|
||||||
let mut state = GLOBAL_STATE.write().unwrap();
|
let mut state = GLOBAL_STATE.write().unwrap();
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use crate::utils::tls::CertificateConfig;
|
|||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use notify::{event::ModifyKind, Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
|
use notify::{event::ModifyKind, Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
use port_check::is_port_reachable;
|
|
||||||
use privdrop::PrivDrop;
|
use privdrop::PrivDrop;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
@@ -12,6 +11,7 @@ use std::any::type_name;
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
use std::net::TcpListener;
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
@@ -268,14 +268,14 @@ pub fn drop_priv(user: String, group: String, http_addr: String, tls_addr: Optio
|
|||||||
thread::sleep(time::Duration::from_millis(10));
|
thread::sleep(time::Duration::from_millis(10));
|
||||||
loop {
|
loop {
|
||||||
thread::sleep(time::Duration::from_millis(10));
|
thread::sleep(time::Duration::from_millis(10));
|
||||||
if is_port_reachable(http_addr.clone()) {
|
if port_is_available(http_addr.clone()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(tls_addr) = tls_addr {
|
if let Some(tls_addr) = tls_addr {
|
||||||
loop {
|
loop {
|
||||||
thread::sleep(time::Duration::from_millis(10));
|
thread::sleep(time::Duration::from_millis(10));
|
||||||
if is_port_reachable(tls_addr.clone()) {
|
if port_is_available(tls_addr.clone()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,6 +287,13 @@ pub fn drop_priv(user: String, group: String, http_addr: String, tls_addr: Optio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn port_is_available(addr: String) -> bool {
|
||||||
|
match TcpListener::bind(addr) {
|
||||||
|
Ok(_) => false,
|
||||||
|
Err(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn check_priv(addr: &str) {
|
pub fn check_priv(addr: &str) {
|
||||||
let port = SocketAddr::from_str(addr).map(|sa| sa.port()).unwrap();
|
let port = SocketAddr::from_str(addr).map(|sa| sa.port()).unwrap();
|
||||||
match port < 1024 {
|
match port < 1024 {
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
use crate::utils::auth::authenticate;
|
use crate::utils::auth::authenticate;
|
||||||
use crate::utils::metrics::*;
|
use crate::utils::metrics::*;
|
||||||
use crate::utils::structs::{AppConfig, Extraparams, Headers, InnerMap, UpstreamsDashMap, UpstreamsIdMap};
|
use crate::utils::structs::{AppConfig, Extraparams, Headers, InnerMap, UpstreamsDashMap, UpstreamsIdMap};
|
||||||
// use std::collections::BTreeMap;
|
|
||||||
use crate::web::gethosts::{GetHost, GetHostsReturHeaders};
|
use crate::web::gethosts::{GetHost, GetHostsReturHeaders};
|
||||||
use arc_swap::ArcSwap;
|
use arc_swap::ArcSwap;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use axum::body::Bytes;
|
use axum::body::Bytes;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
// use x509_parser::asn1_rs::ToDer;
|
|
||||||
use itoa::Buffer;
|
|
||||||
use log::{debug, error, warn};
|
use log::{debug, error, warn};
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use pingora::http::{RequestHeader, ResponseHeader, StatusCode};
|
use pingora::http::{RequestHeader, ResponseHeader, StatusCode};
|
||||||
use pingora::prelude::*;
|
use pingora::prelude::*;
|
||||||
use pingora::ErrorSource::Upstream;
|
use pingora::ErrorSource::Upstream;
|
||||||
@@ -19,16 +15,19 @@ use pingora_core::prelude::HttpPeer;
|
|||||||
// use pingora_core::protocols::TcpKeepalive;
|
// use pingora_core::protocols::TcpKeepalive;
|
||||||
use pingora_limits::rate::Rate;
|
use pingora_limits::rate::Rate;
|
||||||
use pingora_proxy::{ProxyHttp, Session};
|
use pingora_proxy::{ProxyHttp, Session};
|
||||||
|
// use prometheus::{register_int_counter, IntCounter};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, LazyLock};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
|
|
||||||
static RATE_LIMITER: Lazy<Rate> = Lazy::new(|| Rate::new(Duration::from_secs(1)));
|
// static RATE_LIMITER: Lazy<Rate> = Lazy::new(|| Rate::new(Duration::from_secs(1)));
|
||||||
static REVERSE_STORE: Lazy<DashMap<String, String>> = Lazy::new(|| DashMap::new());
|
// static REVERSE_STORE: Lazy<DashMap<String, String>> = Lazy::new(|| DashMap::new());
|
||||||
|
static REVERSE_STORE: LazyLock<DashMap<String, String>> = LazyLock::new(|| DashMap::new());
|
||||||
thread_local! {static IP_BUFFER: RefCell<String> = RefCell::new(String::with_capacity(50));}
|
thread_local! {static IP_BUFFER: RefCell<String> = RefCell::new(String::with_capacity(50));}
|
||||||
|
pub static RATE_LIMITER: LazyLock<Rate> = LazyLock::new(|| Rate::new(Duration::from_secs(1)));
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LB {
|
pub struct LB {
|
||||||
@@ -102,12 +101,7 @@ impl ProxyHttp for LB {
|
|||||||
let rate_key = session.client_addr().and_then(|addr| addr.as_inet()).map(|inet| inet.ip());
|
let rate_key = session.client_addr().and_then(|addr| addr.as_inet()).map(|inet| inet.ip());
|
||||||
let curr_window_requests = RATE_LIMITER.observe(&rate_key, 1);
|
let curr_window_requests = RATE_LIMITER.observe(&rate_key, 1);
|
||||||
if curr_window_requests > rate {
|
if curr_window_requests > rate {
|
||||||
let mut buf = Buffer::new();
|
let header = ResponseHeader::build(429, None)?;
|
||||||
let rate_str = buf.format(rate);
|
|
||||||
let mut header = ResponseHeader::build(429, None)?;
|
|
||||||
header.insert_header("X-Rate-Limit-Limit", rate_str)?;
|
|
||||||
header.insert_header("X-Rate-Limit-Remaining", "0")?;
|
|
||||||
header.insert_header("X-Rate-Limit-Reset", "1")?;
|
|
||||||
session.set_keepalive(None);
|
session.set_keepalive(None);
|
||||||
session.write_response_header(Box::new(header), true).await?;
|
session.write_response_header(Box::new(header), true).await?;
|
||||||
debug!("Rate limited: {:?}, {}", rate_key, rate);
|
debug!("Rate limited: {:?}, {}", rate_key, rate);
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ async fn conf(State(st): State<AppState>, Query(params): Query<HashMap<String, S
|
|||||||
if let Some(s) = params.get("key") {
|
if let Some(s) = params.get("key") {
|
||||||
if s.to_owned() == st.master_key {
|
if s.to_owned() == st.master_key {
|
||||||
let strcontent = content.as_str();
|
let strcontent = content.as_str();
|
||||||
let parsed = serde_yaml::from_str::<Config>(strcontent);
|
let parsed = serde_yml::from_str::<Config>(strcontent);
|
||||||
match parsed {
|
match parsed {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
if let Some(s) = params.get("key") {
|
if let Some(s) = params.get("key") {
|
||||||
|
|||||||
Reference in New Issue
Block a user