Skip to content

Commit

Permalink
feat(local): load servers from remote before running
Browse files Browse the repository at this point in the history
  • Loading branch information
zonyitoo committed May 12, 2024
1 parent 09040e5 commit 200463d
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 63 deletions.
2 changes: 1 addition & 1 deletion crates/shadowsocks-service/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2557,7 +2557,7 @@ impl Config {
}
}

if self.config_type.is_server() && self.server.is_empty() {
if self.config_type.is_server() && self.server.is_empty() && self.online_config.is_none() {
let err = Error::new(
ErrorKind::MissingField,
"missing any valid servers in configuration",
Expand Down
147 changes: 85 additions & 62 deletions src/service/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ pub fn define_command_line_options(mut app: Command) -> Command {

/// Create `Runtime` and `main` entry
pub fn create(matches: &ArgMatches) -> Result<(Runtime, impl Future<Output = ExitCode>), ExitCode> {
let (config, runtime) = {
let (mut config, runtime) = {
let config_path_opt = matches.get_one::<PathBuf>("CONFIG").cloned().or_else(|| {
if !matches.contains_id("SERVER_CONFIG") {
match crate::config::get_default_config_path("local.json") {
Expand Down Expand Up @@ -1000,6 +1000,20 @@ pub fn create(matches: &ArgMatches) -> Result<(Runtime, impl Future<Output = Exi
}
}

// Fetch servers from remote for the first time
#[cfg(feature = "local-online-config")]
if let Some(ref online_config) = config.online_config {
if let Ok(mut servers) = get_online_config_servers(&online_config.config_url).await {
config.server.append(&mut servers);
}
}

// Double check
if config.server.is_empty() {
eprintln!("local server cannot run without any valid servers");
return crate::EXIT_CODE_LOAD_CONFIG_FAILURE.into();
}

#[cfg(feature = "local-online-config")]
let (online_config_url, online_config_update_interval) = match config.online_config.clone() {
Some(o) => (Some(o.config_url), o.update_interval),
Expand Down Expand Up @@ -1052,7 +1066,7 @@ pub fn create(matches: &ArgMatches) -> Result<(Runtime, impl Future<Output = Exi
}
_ = reload_task => {
// continue.
trace!("server-reloader task exited");
trace!("server-loader task task exited");
}
}
}
Expand Down Expand Up @@ -1080,6 +1094,67 @@ struct ServerReloader {
online_config_update_interval: Option<Duration>,
}

#[cfg(feature = "local-online-config")]
async fn get_online_config_servers(
online_config_url: &str,
) -> Result<Vec<ServerInstanceConfig>, Box<dyn std::error::Error>> {
use log::warn;

#[inline]
async fn get_online_config(online_config_url: &str) -> reqwest::Result<String> {
let response = reqwest::get(online_config_url).await?;
if response.url().scheme() != "https" {
warn!(
"SIP008 suggests configuration URL should use https, but current URL is {}",
response.url().scheme()
);
}

// Content-Type: application/json; charset=utf-8
// mandatory in standard SIP008
match response.headers().get("Content-Type") {
Some(h) => {
if h != "application/json; charset=utf-8" {
warn!(
"SIP008 Content-Type must be \"application/json; charset=utf-8\", but found {}",
h.to_str().unwrap_or("[non-utf8-value]")
);
}
}
None => {
warn!("missing Content-Type in SIP008 response from {}", online_config_url);
}
}

response.text().await
}

let body = match get_online_config(online_config_url).await {
Ok(b) => b,
Err(err) => {
error!(
"server-loader task failed to load from url: {}, error: {}",
online_config_url, err
);
return Err(Box::new(err));
}
};

// NOTE: ConfigType::Local will force verify local_address and local_port keys. SIP008 standard doesn't include those keys.
let online_config = match Config::load_from_str(&body, ConfigType::Server) {
Ok(c) => c,
Err(err) => {
error!(
"server-loader task failed to load from url: {}, error: {}",
online_config_url, err
);
return Err(Box::new(err));
}
};

Ok(online_config.server)
}

impl ServerReloader {
async fn run_once(&self) -> Result<(), Box<dyn std::error::Error>> {
let start_time = Instant::now();
Expand All @@ -1092,7 +1167,7 @@ impl ServerReloader {
Ok(c) => c,
Err(err) => {
error!(
"server-reloader failed to load from file: {}, error: {}",
"server-loader task failed to load from file: {}, error: {}",
config_path.display(),
err
);
Expand All @@ -1105,60 +1180,8 @@ impl ServerReloader {
// Load servers from online-config (SIP008)
#[cfg(feature = "local-online-config")]
if let Some(ref online_config_url) = self.online_config_url {
use log::warn;

#[inline]
async fn get_online_config(online_config_url: &str) -> reqwest::Result<String> {
let response = reqwest::get(online_config_url).await?;
if response.url().scheme() != "https" {
warn!(
"SIP008 suggests configuration URL should use https, but current URL is {}",
response.url().scheme()
);
}

// Content-Type: application/json; charset=utf-8
// mandatory in standard SIP008
match response.headers().get("Content-Type") {
Some(h) => {
if h != "application/json; charset=utf-8" {
warn!(
"SIP008 Content-Type must be \"application/json; charset=utf-8\", but found {}",
h.to_str().unwrap_or("[non-utf8-value]")
);
}
}
None => {
warn!("missing Content-Type in SIP008 response from {}", online_config_url);
}
}

response.text().await
}

let body = match get_online_config(online_config_url).await {
Ok(b) => b,
Err(err) => {
error!(
"server-reloader failed to load from url: {}, error: {}",
online_config_url, err
);
return Err(Box::new(err));
}
};

// NOTE: ConfigType::Local will force verify local_address and local_port keys. SIP008 standard doesn't include those keys.
let mut online_config = match Config::load_from_str(&body, ConfigType::Server) {
Ok(c) => c,
Err(err) => {
error!(
"server-reloader failed to load from url: {}, error: {}",
online_config_url, err
);
return Err(Box::new(err));
}
};
servers.append(&mut online_config.server);
let mut online_servers = get_online_config_servers(&online_config_url).await?;

Check warning on line 1183 in src/service/local.rs

View workflow job for this annotation

GitHub Actions / clippy ubuntu-latest

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> src/service/local.rs:1183:64 | 1183 | let mut online_servers = get_online_config_servers(&online_config_url).await?; | ^^^^^^^^^^^^^^^^^^ help: change this to: `online_config_url` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow = note: `#[warn(clippy::needless_borrow)]` on by default

Check warning on line 1183 in src/service/local.rs

View workflow job for this annotation

GitHub Actions / clippy macos-latest

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> src/service/local.rs:1183:64 | 1183 | let mut online_servers = get_online_config_servers(&online_config_url).await?; | ^^^^^^^^^^^^^^^^^^ help: change this to: `online_config_url` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow = note: `#[warn(clippy::needless_borrow)]` on by default
servers.append(&mut online_servers);
}

let server_len = servers.len();
Expand Down Expand Up @@ -1188,14 +1211,14 @@ impl ServerReloader {
let fetch_end_time = Instant::now();

if let Err(err) = self.balancer.reset_servers(servers).await {
error!("server-reloader {} servers but found error: {}", server_len, err);
error!("server-loader task {} servers but found error: {}", server_len, err);
return Err(Box::new(err));
}

let total_end_time = Instant::now();

info!(
"server-reloader reload from {} with {} servers, fetch costs: {:?}, total costs: {:?}",
"server-loader task reload from {} with {} servers, fetch costs: {:?}, total costs: {:?}",
ConfigDisplay(&self),

Check warning on line 1222 in src/service/local.rs

View workflow job for this annotation

GitHub Actions / clippy ubuntu-latest

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> src/service/local.rs:1222:27 | 1222 | ConfigDisplay(&self), | ^^^^^ help: change this to: `self` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow

Check warning on line 1222 in src/service/local.rs

View workflow job for this annotation

GitHub Actions / clippy macos-latest

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> src/service/local.rs:1222:27 | 1222 | ConfigDisplay(&self), | ^^^^^ help: change this to: `self` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
server_len,
fetch_end_time - start_time,
Expand All @@ -1212,7 +1235,7 @@ impl ServerReloader {

let mut sigusr1 = signal(SignalKind::user_defined1()).expect("signal");

debug!("server-reloader is now listening USR1");
debug!("server-loader task is now listening USR1");

while sigusr1.recv().await.is_some() {
let _ = self.run_once().await;
Expand All @@ -1228,11 +1251,11 @@ impl ServerReloader {
.online_config_update_interval
.unwrap_or(Duration::from_secs(1 * 60 * 60));

Check warning on line 1252 in src/service/local.rs

View workflow job for this annotation

GitHub Actions / clippy ubuntu-latest

this operation has no effect

warning: this operation has no effect --> src/service/local.rs:1252:44 | 1252 | .unwrap_or(Duration::from_secs(1 * 60 * 60)); | ^^^^^^ help: consider reducing it to: `60` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#identity_op = note: `#[warn(clippy::identity_op)]` on by default

Check warning on line 1252 in src/service/local.rs

View workflow job for this annotation

GitHub Actions / clippy macos-latest

this operation has no effect

warning: this operation has no effect --> src/service/local.rs:1252:44 | 1252 | .unwrap_or(Duration::from_secs(1 * 60 * 60)); | ^^^^^^ help: consider reducing it to: `60` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#identity_op = note: `#[warn(clippy::identity_op)]` on by default

debug!("server-reloader updating in interval {:?}", update_interval);
debug!("server-loader task updating in interval {:?}", update_interval);

loop {
let _ = self.run_once().await;
time::sleep(update_interval).await;
let _ = self.run_once().await;
}
}

Expand Down

0 comments on commit 200463d

Please sign in to comment.