mirror of https://github.com/rwf2/Rocket.git
parent
4bc5c20a45
commit
d4d5c5dd29
|
@ -1,7 +1,7 @@
|
|||
use std::collections::{HashMap, BTreeMap};
|
||||
use std::hash::Hash;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use config::{Result, Config, Value, Environment};
|
||||
use config::toml_ext::IntoValue;
|
||||
use logger::LoggingLevel;
|
||||
|
||||
/// The core configuration structure.
|
||||
|
@ -111,69 +111,3 @@ impl ConfigBuilder {
|
|||
self.finalize().expect("ConfigBuilder::unwrap() failed")
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoValue {
|
||||
fn into_value(self) -> Value;
|
||||
}
|
||||
|
||||
impl<'a> IntoValue for &'a str {
|
||||
fn into_value(self) -> Value {
|
||||
Value::String(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoValue for Value {
|
||||
fn into_value(self) -> Value {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: IntoValue> IntoValue for Vec<V> {
|
||||
fn into_value(self) -> Value {
|
||||
Value::Array(self.into_iter().map(|v| v.into_value()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Into<String>, V: IntoValue> IntoValue for BTreeMap<S, V> {
|
||||
fn into_value(self) -> Value {
|
||||
let table = self.into_iter()
|
||||
.map(|(s, v)| (s.into(), v.into_value()))
|
||||
.collect();
|
||||
|
||||
Value::Table(table)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Into<String> + Hash + Eq, V: IntoValue> IntoValue for HashMap<S, V> {
|
||||
fn into_value(self) -> Value {
|
||||
let table = self.into_iter()
|
||||
.map(|(s, v)| (s.into(), v.into_value()))
|
||||
.collect();
|
||||
|
||||
Value::Table(table)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_into_value {
|
||||
($variant:ident : $t:ty) => ( impl_into_value!($variant: $t,); );
|
||||
|
||||
($variant:ident : $t:ty, $($extra:tt)*) => (
|
||||
impl IntoValue for $t {
|
||||
fn into_value(self) -> Value {
|
||||
Value::$variant(self $($extra)*)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
impl_into_value!(String: String);
|
||||
impl_into_value!(Integer: i64);
|
||||
impl_into_value!(Integer: isize, as i64);
|
||||
impl_into_value!(Integer: i32, as i64);
|
||||
impl_into_value!(Integer: i8, as i64);
|
||||
impl_into_value!(Integer: u8, as i64);
|
||||
impl_into_value!(Integer: u32, as i64);
|
||||
impl_into_value!(Boolean: bool);
|
||||
impl_into_value!(Float: f64);
|
||||
impl_into_value!(Float: f32, as f64);
|
||||
|
||||
|
|
|
@ -47,6 +47,10 @@ pub enum ConfigError {
|
|||
///
|
||||
/// Parameters: (toml_source_string, filename, error_list)
|
||||
ParseError(String, PathBuf, Vec<ParsingError>),
|
||||
/// There was a TOML parsing error in a config environment variable.
|
||||
///
|
||||
/// Parameters: (env_key, env_value, expected type)
|
||||
BadEnvVal(String, String, &'static str),
|
||||
}
|
||||
|
||||
impl ConfigError {
|
||||
|
@ -90,6 +94,12 @@ impl ConfigError {
|
|||
trace_!("'{}' - {}", error_source, White.paint(&error.desc));
|
||||
}
|
||||
}
|
||||
BadEnvVal(ref key, ref value, ref expected) => {
|
||||
error!("environment variable '{}={}' could not be parsed",
|
||||
White.paint(key), White.paint(value));
|
||||
info_!("value for {:?} must be {}",
|
||||
White.paint(key), White.paint(expected))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -101,6 +101,20 @@
|
|||
//! address = "0.0.0.0"
|
||||
//! ```
|
||||
//!
|
||||
//! ## Environment Variables
|
||||
//!
|
||||
//! All configuration parameters, including extras, can be overridden through
|
||||
//! environment variables. To override the configuration parameter `param`, use
|
||||
//! an environment variable named `ROCKET_PARAM`. For instance, to override the
|
||||
//! "port" configuration parameter, you can run your application with:
|
||||
//!
|
||||
//! ```sh
|
||||
//! ROCKET_PORT=3721 ./your_application
|
||||
//! ```
|
||||
//!
|
||||
//! Environment variables take precedence over all other configuration methods:
|
||||
//! if the variable is set, it will be used as the value for the parameter.
|
||||
//!
|
||||
//! ## Retrieving Configuration Parameters
|
||||
//!
|
||||
//! Configuration parameters for the currently active configuration environment
|
||||
|
@ -133,6 +147,7 @@ mod error;
|
|||
mod environment;
|
||||
mod config;
|
||||
mod builder;
|
||||
mod toml_ext;
|
||||
|
||||
use std::sync::{Once, ONCE_INIT};
|
||||
use std::fs::{self, File};
|
||||
|
@ -149,15 +164,21 @@ pub use self::error::{ConfigError, ParsingError};
|
|||
pub use self::environment::Environment;
|
||||
pub use self::config::Config;
|
||||
pub use self::builder::ConfigBuilder;
|
||||
pub use self::toml_ext::IntoValue;
|
||||
|
||||
use self::Environment::*;
|
||||
use self::environment::CONFIG_ENV;
|
||||
use self::toml_ext::parse_simple_toml_value;
|
||||
use logger::{self, LoggingLevel};
|
||||
use http::ascii::uncased_eq;
|
||||
|
||||
static INIT: Once = ONCE_INIT;
|
||||
static mut CONFIG: Option<RocketConfig> = None;
|
||||
|
||||
const CONFIG_FILENAME: &'static str = "Rocket.toml";
|
||||
const GLOBAL_ENV_NAME: &'static str = "global";
|
||||
const ENV_VAR_PREFIX: &'static str = "ROCKET_";
|
||||
const PREHANDLED_VARS: [&'static str; 2] = ["ROCKET_CODEGEN_DEBUG", CONFIG_ENV];
|
||||
|
||||
/// Wraps `std::result` with the error type of
|
||||
/// [ConfigError](enum.ConfigError.html).
|
||||
|
@ -171,8 +192,15 @@ pub struct RocketConfig {
|
|||
}
|
||||
|
||||
impl RocketConfig {
|
||||
/// TODO: Doc.
|
||||
fn new(config: Config) -> RocketConfig {
|
||||
/// Create a new configuration using the passed in `config` for all
|
||||
/// environments. The Rocket.toml file is ignored, as are environment
|
||||
/// variables.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the current working directory can't be retrieved, this function
|
||||
/// panics.
|
||||
pub fn new(config: Config) -> RocketConfig {
|
||||
let f = config.config_path.clone();
|
||||
let active_env = config.environment;
|
||||
|
||||
|
@ -190,6 +218,41 @@ impl RocketConfig {
|
|||
}
|
||||
}
|
||||
|
||||
/// Read the configuration from the `Rocket.toml` file. The file is search
|
||||
/// for recursively up the tree, starting from the CWD.
|
||||
pub fn read() -> Result<RocketConfig> {
|
||||
// Find the config file, starting from the `cwd` and working backwords.
|
||||
let file = RocketConfig::find()?;
|
||||
|
||||
// Try to open the config file for reading.
|
||||
let mut handle = File::open(&file).map_err(|_| ConfigError::IOError)?;
|
||||
|
||||
// Read the configure file to a string for parsing.
|
||||
let mut contents = String::new();
|
||||
handle.read_to_string(&mut contents).map_err(|_| ConfigError::IOError)?;
|
||||
|
||||
// Parse the config and return the result.
|
||||
RocketConfig::parse(contents, &file)
|
||||
}
|
||||
|
||||
/// Return the default configuration for all environments and marks the
|
||||
/// active environment (via the CONFIG_ENV variable) as active.
|
||||
pub fn active_default<P: AsRef<Path>>(filename: P) -> Result<RocketConfig> {
|
||||
let mut defaults = HashMap::new();
|
||||
defaults.insert(Development, Config::default_for(Development, &filename)?);
|
||||
defaults.insert(Staging, Config::default_for(Staging, &filename)?);
|
||||
defaults.insert(Production, Config::default_for(Production, &filename)?);
|
||||
|
||||
let mut config = RocketConfig {
|
||||
active_env: Environment::active()?,
|
||||
config: defaults,
|
||||
};
|
||||
|
||||
// Override any variables from the environment.
|
||||
config.override_from_env()?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Iteratively search for `CONFIG_FILENAME` starting at the current working
|
||||
/// directory and working up through its parents. Returns the path to the
|
||||
/// file or an Error::NoKey if the file couldn't be found. If the current
|
||||
|
@ -213,18 +276,20 @@ impl RocketConfig {
|
|||
Err(ConfigError::NotFound)
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, env: Environment) -> &mut Config {
|
||||
match self.config.get_mut(&env) {
|
||||
Some(config) => config,
|
||||
None => panic!("set(): {} config is missing.", env),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the configuration for the environment `env` to be the configuration
|
||||
/// derived from the TOML table `kvs`. The environment must already exist in
|
||||
/// `self`, otherwise this function panics. Any existing values are
|
||||
/// overriden by those in `kvs`.
|
||||
fn set(&mut self, env: Environment, kvs: &Table) -> Result<()> {
|
||||
let config = match self.config.get_mut(&env) {
|
||||
Some(config) => config,
|
||||
None => panic!("set(): {} config is missing.", env),
|
||||
};
|
||||
|
||||
fn set_from_table(&mut self, env: Environment, kvs: &Table) -> Result<()> {
|
||||
for (key, value) in kvs {
|
||||
config.set(key, value)?;
|
||||
self.get_mut(env).set(key, value)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -243,6 +308,41 @@ impl RocketConfig {
|
|||
self.get(self.active_env)
|
||||
}
|
||||
|
||||
// Override all environments with values from env variables if present.
|
||||
fn override_from_env(&mut self) -> Result<()> {
|
||||
'outer: for (env_key, env_val) in env::vars() {
|
||||
if env_key.len() < ENV_VAR_PREFIX.len() {
|
||||
continue
|
||||
} else if !uncased_eq(&env_key[..ENV_VAR_PREFIX.len()], ENV_VAR_PREFIX) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip environment variables that are handled elsewhere.
|
||||
for prehandled_var in PREHANDLED_VARS.iter() {
|
||||
if uncased_eq(&env_key, &prehandled_var) {
|
||||
continue 'outer
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the key and value and try to set the variable for all envs.
|
||||
let key = env_key[ENV_VAR_PREFIX.len()..].to_lowercase();
|
||||
let val = parse_simple_toml_value(&env_val);
|
||||
for env in &Environment::all() {
|
||||
match self.get_mut(*env).set(&key, &val) {
|
||||
Err(ConfigError::BadType(_, exp, _, _)) => {
|
||||
return Err(ConfigError::BadEnvVal(env_key, env_val, exp))
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
Ok(_) => { /* move along */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses the configuration from the Rocket.toml file. Also overrides any
|
||||
/// values there with values from the environment.
|
||||
fn parse<P: AsRef<Path>>(src: String, filename: P) -> Result<RocketConfig> {
|
||||
// Get a PathBuf version of the filename.
|
||||
let path = filename.as_ref().to_path_buf();
|
||||
|
@ -287,7 +387,7 @@ impl RocketConfig {
|
|||
// This is not the global table. Parse the environment name from the
|
||||
// table entry name and then set all of the key/values.
|
||||
match entry.as_str().parse() {
|
||||
Ok(env) => config.set(env, kv_pairs)?,
|
||||
Ok(env) => config.set_from_table(env, kv_pairs)?,
|
||||
Err(_) => Err(ConfigError::BadEntry(entry.clone(), path.clone()))?
|
||||
}
|
||||
}
|
||||
|
@ -295,39 +395,15 @@ impl RocketConfig {
|
|||
// Override all of the environments with the global values.
|
||||
if let Some(ref global_kv_pairs) = global {
|
||||
for env in &Environment::all() {
|
||||
config.set(*env, global_kv_pairs)?;
|
||||
config.set_from_table(*env, global_kv_pairs)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Override any variables from the environment.
|
||||
config.override_from_env()?;
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn read() -> Result<RocketConfig> {
|
||||
// Find the config file, starting from the `cwd` and working backwords.
|
||||
let file = RocketConfig::find()?;
|
||||
|
||||
// Try to open the config file for reading.
|
||||
let mut handle = File::open(&file).map_err(|_| ConfigError::IOError)?;
|
||||
|
||||
// Read the configure file to a string for parsing.
|
||||
let mut contents = String::new();
|
||||
handle.read_to_string(&mut contents).map_err(|_| ConfigError::IOError)?;
|
||||
|
||||
// Parse the contents from the file.
|
||||
RocketConfig::parse(contents, &file)
|
||||
}
|
||||
|
||||
pub fn active_default<P: AsRef<Path>>(filename: P) -> Result<RocketConfig> {
|
||||
let mut defaults = HashMap::new();
|
||||
defaults.insert(Development, Config::default_for(Development, &filename)?);
|
||||
defaults.insert(Staging, Config::default_for(Staging, &filename)?);
|
||||
defaults.insert(Production, Config::default_for(Production, &filename)?);
|
||||
|
||||
Ok(RocketConfig {
|
||||
active_env: Environment::active()?,
|
||||
config: defaults,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the active configuration and whether this call initialized the
|
||||
|
@ -382,7 +458,7 @@ unsafe fn private_init() {
|
|||
let config = RocketConfig::read().unwrap_or_else(|e| {
|
||||
match e {
|
||||
ParseError(..) | BadEntry(..) | BadEnv(..) | BadType(..)
|
||||
| BadFilePath(..) => bail(e),
|
||||
| BadFilePath(..) | BadEnvVal(..) => bail(e),
|
||||
IOError | BadCWD => warn!("Failed reading Rocket.toml. Using defaults."),
|
||||
NotFound => { /* try using the default below */ }
|
||||
}
|
||||
|
@ -412,7 +488,7 @@ mod test {
|
|||
use std::env;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use super::{RocketConfig, ConfigError, ConfigBuilder};
|
||||
use super::{RocketConfig, Config, ConfigError, ConfigBuilder};
|
||||
use super::{Environment, GLOBAL_ENV_NAME};
|
||||
use super::environment::CONFIG_ENV;
|
||||
use super::Environment::*;
|
||||
|
@ -876,4 +952,88 @@ mod test {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_env_override() {
|
||||
// Take the lock so changing the environment doesn't cause races.
|
||||
let _env_lock = ENV_LOCK.lock().unwrap();
|
||||
|
||||
let pairs = [
|
||||
("log", "critical"), ("LOG", "debug"), ("PORT", "8110"),
|
||||
("address", "1.2.3.4"), ("EXTRA_EXTRA", "true"), ("workers", "3")
|
||||
];
|
||||
|
||||
let check_value = |key: &str, val: &str, config: &Config| {
|
||||
match key {
|
||||
"log" => assert_eq!(config.log_level, val.parse().unwrap()),
|
||||
"port" => assert_eq!(config.port, val.parse().unwrap()),
|
||||
"address" => assert_eq!(config.address, val),
|
||||
"extra_extra" => assert_eq!(config.get_bool(key).unwrap(), true),
|
||||
"workers" => assert_eq!(config.workers, val.parse().unwrap()),
|
||||
_ => panic!("Unexpected key: {}", key)
|
||||
}
|
||||
};
|
||||
|
||||
// Check that setting the environment variable actually changes the
|
||||
// config for the default active and nonactive environments.
|
||||
for &(key, val) in &pairs {
|
||||
env::set_var(format!("ROCKET_{}", key), val);
|
||||
|
||||
let rconfig = active_default().unwrap();
|
||||
// Check that it overrides the active config.
|
||||
for env in &Environment::all() {
|
||||
env::set_var(CONFIG_ENV, env.to_string());
|
||||
let rconfig = active_default().unwrap();
|
||||
check_value(&*key.to_lowercase(), val, rconfig.active());
|
||||
}
|
||||
|
||||
// And non-active configs.
|
||||
for env in &Environment::all() {
|
||||
check_value(&*key.to_lowercase(), val, rconfig.get(*env));
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the variables so they don't override for the next test.
|
||||
for &(key, _) in &pairs {
|
||||
env::remove_var(format!("ROCKET_{}", key))
|
||||
}
|
||||
|
||||
// Now we build a config file to test that the environment variables
|
||||
// override configurations from files as well.
|
||||
let toml = r#"
|
||||
[dev]
|
||||
address = "1.2.3.4"
|
||||
|
||||
[stage]
|
||||
address = "2.3.4.5"
|
||||
|
||||
[prod]
|
||||
address = "10.1.1.1"
|
||||
|
||||
[global]
|
||||
address = "1.2.3.4"
|
||||
port = 7810
|
||||
workers = 21
|
||||
log = "normal"
|
||||
"#.to_string();
|
||||
|
||||
// Check that setting the environment variable actually changes the
|
||||
// config for the default active environments.
|
||||
for &(key, val) in &pairs {
|
||||
env::set_var(format!("ROCKET_{}", key), val);
|
||||
|
||||
let r = RocketConfig::parse(toml.clone(), TEST_CONFIG_FILENAME).unwrap();
|
||||
check_value(&*key.to_lowercase(), val, r.active());
|
||||
|
||||
// And non-active configs.
|
||||
for env in &Environment::all() {
|
||||
check_value(&*key.to_lowercase(), val, r.get(*env));
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the variables so they don't override for the next test.
|
||||
for &(key, _) in &pairs {
|
||||
env::remove_var(format!("ROCKET_{}", key))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
use std::collections::{HashMap, BTreeMap};
|
||||
use std::hash::Hash;
|
||||
use std::str::FromStr;
|
||||
|
||||
use config::Value;
|
||||
|
||||
pub fn parse_simple_toml_value(string: &str) -> Value {
|
||||
if let Ok(int) = i64::from_str(string) {
|
||||
return Value::Integer(int)
|
||||
}
|
||||
|
||||
if let Ok(boolean) = bool::from_str(string) {
|
||||
return Value::Boolean(boolean)
|
||||
}
|
||||
|
||||
if let Ok(float) = f64::from_str(string) {
|
||||
return Value::Float(float)
|
||||
}
|
||||
|
||||
Value::String(string.to_string())
|
||||
}
|
||||
|
||||
pub trait IntoValue {
|
||||
fn into_value(self) -> Value;
|
||||
}
|
||||
|
||||
impl<'a> IntoValue for &'a str {
|
||||
fn into_value(self) -> Value {
|
||||
Value::String(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoValue for Value {
|
||||
fn into_value(self) -> Value {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: IntoValue> IntoValue for Vec<V> {
|
||||
fn into_value(self) -> Value {
|
||||
Value::Array(self.into_iter().map(|v| v.into_value()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Into<String>, V: IntoValue> IntoValue for BTreeMap<S, V> {
|
||||
fn into_value(self) -> Value {
|
||||
let table = self.into_iter()
|
||||
.map(|(s, v)| (s.into(), v.into_value()))
|
||||
.collect();
|
||||
|
||||
Value::Table(table)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Into<String> + Hash + Eq, V: IntoValue> IntoValue for HashMap<S, V> {
|
||||
fn into_value(self) -> Value {
|
||||
let table = self.into_iter()
|
||||
.map(|(s, v)| (s.into(), v.into_value()))
|
||||
.collect();
|
||||
|
||||
Value::Table(table)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_into_value {
|
||||
($variant:ident : $t:ty) => ( impl_into_value!($variant: $t,); );
|
||||
|
||||
($variant:ident : $t:ty, $($extra:tt)*) => (
|
||||
impl IntoValue for $t {
|
||||
fn into_value(self) -> Value {
|
||||
Value::$variant(self $($extra)*)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
impl_into_value!(String: String);
|
||||
impl_into_value!(Integer: i64);
|
||||
impl_into_value!(Integer: isize, as i64);
|
||||
impl_into_value!(Integer: i32, as i64);
|
||||
impl_into_value!(Integer: i8, as i64);
|
||||
impl_into_value!(Integer: u8, as i64);
|
||||
impl_into_value!(Integer: u32, as i64);
|
||||
impl_into_value!(Boolean: bool);
|
||||
impl_into_value!(Float: f64);
|
||||
impl_into_value!(Float: f32, as f64);
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
//! Rocket's logging infrastructure.
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::fmt;
|
||||
|
||||
use log::{self, Log, LogLevel, LogRecord, LogMetadata};
|
||||
use term_painter::Color::*;
|
||||
|
@ -44,6 +45,18 @@ impl FromStr for LoggingLevel {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LoggingLevel {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let string = match *self {
|
||||
LoggingLevel::Critical => "critical",
|
||||
LoggingLevel::Normal => "normal",
|
||||
LoggingLevel::Debug => "debug",
|
||||
};
|
||||
|
||||
write!(f, "{}", string)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)] #[macro_export]
|
||||
macro_rules! log_ {
|
||||
($name:ident: $format:expr) => { log_!($name: $format,) };
|
||||
|
|
|
@ -300,6 +300,7 @@ impl Rocket {
|
|||
|
||||
/// Creates a new `Rocket` application using the supplied custom
|
||||
/// configuration information. The `Rocket.toml` file, if present, is
|
||||
/// ignored. Any environment variables setting config parameters are
|
||||
/// ignored. If `log` is `true`, logging is enabled.
|
||||
///
|
||||
/// This method is typically called through the `rocket::custom` alias.
|
||||
|
@ -331,10 +332,9 @@ impl Rocket {
|
|||
}
|
||||
|
||||
info!("🔧 Configured for {}.", config.environment);
|
||||
info_!("listening: {}:{}",
|
||||
White.paint(&config.address),
|
||||
White.paint(&config.port));
|
||||
info_!("logging: {:?}", White.paint(config.log_level));
|
||||
info_!("address: {}", White.paint(&config.address));
|
||||
info_!("port: {}", White.paint(&config.port));
|
||||
info_!("log: {}", White.paint(config.log_level));
|
||||
info_!("workers: {}", White.paint(config.workers));
|
||||
|
||||
let session_key = config.take_session_key();
|
||||
|
|
Loading…
Reference in New Issue