mirror of
https://github.com/amigan/rustypaste-pretty.git
synced 2024-11-22 04:19:47 -05:00
feat(config): hot-reload the configuration
This commit is contained in:
parent
f078a9afa7
commit
a2de1c3334
6 changed files with 176 additions and 18 deletions
118
Cargo.lock
generated
118
Cargo.lock
generated
|
@ -652,6 +652,18 @@ dependencies = [
|
||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filetime"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.20"
|
version = "1.0.20"
|
||||||
|
@ -680,6 +692,25 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fsevent"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"fsevent-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fsevent-sys"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fuchsia-zircon"
|
name = "fuchsia-zircon"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
@ -880,6 +911,16 @@ dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hotwatch"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39301670a6f5798b75f36a1b149a379a50df5aa7c71be50f4b41ec6eab445cb8"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"notify",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
|
@ -930,6 +971,26 @@ version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea70330449622910e0edebab230734569516269fb32342fb0a8956340fa48c6c"
|
checksum = "ea70330449622910e0edebab230734569516269fb32342fb0a8956340fa48c6c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"inotify-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify-sys"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instant"
|
name = "instant"
|
||||||
version = "0.1.10"
|
version = "0.1.10"
|
||||||
|
@ -1029,6 +1090,12 @@ version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazycell"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lexical-core"
|
name = "lexical-core"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
|
@ -1144,6 +1211,18 @@ dependencies = [
|
||||||
"winapi 0.2.8",
|
"winapi 0.2.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio-extras"
|
||||||
|
version = "2.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19"
|
||||||
|
dependencies = [
|
||||||
|
"lazycell",
|
||||||
|
"log",
|
||||||
|
"mio",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio-uds"
|
name = "mio-uds"
|
||||||
version = "0.6.8"
|
version = "0.6.8"
|
||||||
|
@ -1199,6 +1278,24 @@ dependencies = [
|
||||||
"version_check 0.9.3",
|
"version_check 0.9.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify"
|
||||||
|
version = "4.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"filetime",
|
||||||
|
"fsevent",
|
||||||
|
"fsevent-sys",
|
||||||
|
"inotify",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"mio-extras",
|
||||||
|
"walkdir",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.13.0"
|
version = "1.13.0"
|
||||||
|
@ -1549,6 +1646,7 @@ dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"glob",
|
"glob",
|
||||||
|
"hotwatch",
|
||||||
"humantime",
|
"humantime",
|
||||||
"infer",
|
"infer",
|
||||||
"lazy-regex",
|
"lazy-regex",
|
||||||
|
@ -1569,6 +1667,15 @@ version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -2166,6 +2273,17 @@ version = "0.9.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walkdir"
|
||||||
|
version = "2.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.9.0+wasi-snapshot-preview1"
|
version = "0.9.0+wasi-snapshot-preview1"
|
||||||
|
|
|
@ -31,6 +31,7 @@ lazy-regex = "2.2.1"
|
||||||
humantime = "2.1.0"
|
humantime = "2.1.0"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
ring = "0.16.20"
|
ring = "0.16.20"
|
||||||
|
hotwatch = "0.4.5"
|
||||||
|
|
||||||
[dependencies.config]
|
[dependencies.config]
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
|
|
@ -14,7 +14,7 @@ some text
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- File upload & URL shortening
|
- File upload & URL shortening & upload from URL
|
||||||
- supports basic HTTP authentication
|
- supports basic HTTP authentication
|
||||||
- random file names (optional)
|
- random file names (optional)
|
||||||
- pet name (e.g. `capital-mosquito.txt`)
|
- pet name (e.g. `capital-mosquito.txt`)
|
||||||
|
@ -26,6 +26,8 @@ some text
|
||||||
- no duplicate uploads (optional)
|
- no duplicate uploads (optional)
|
||||||
- Single binary
|
- Single binary
|
||||||
- [binary releases](https://github.com/orhun/rustypaste/releases)
|
- [binary releases](https://github.com/orhun/rustypaste/releases)
|
||||||
|
- Simple configuration
|
||||||
|
- supports hot reloading
|
||||||
- Easy to deploy
|
- Easy to deploy
|
||||||
- [docker images](https://hub.docker.com/r/orhunp/rustypaste)
|
- [docker images](https://hub.docker.com/r/orhunp/rustypaste)
|
||||||
- No database
|
- No database
|
||||||
|
@ -184,7 +186,7 @@ http {
|
||||||
|
|
||||||
### Roadmap
|
### Roadmap
|
||||||
|
|
||||||
- Hot reload the configuration file
|
_Nothing here yet! 🎉_
|
||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::mime::MimeMatcher;
|
||||||
use crate::random::RandomURLConfig;
|
use crate::random::RandomURLConfig;
|
||||||
use byte_unit::Byte;
|
use byte_unit::Byte;
|
||||||
use config::{self, ConfigError};
|
use config::{self, ConfigError};
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
/// Configuration values.
|
/// Configuration values.
|
||||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
||||||
|
@ -43,10 +43,10 @@ pub struct PasteConfig {
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
/// Parses the config file and returns the values.
|
/// Parses the config file and returns the values.
|
||||||
pub fn parse(file_name: &str) -> Result<Config, ConfigError> {
|
pub fn parse(path: &Path) -> Result<Config, ConfigError> {
|
||||||
let mut config = config::Config::default();
|
let mut config = config::Config::default();
|
||||||
config
|
config
|
||||||
.merge(config::File::with_name(file_name))?
|
.merge(config::File::from(path))?
|
||||||
.merge(config::Environment::new().separator("__"))?;
|
.merge(config::Environment::new().separator("__"))?;
|
||||||
config.try_into()
|
config.try_into()
|
||||||
}
|
}
|
||||||
|
@ -59,13 +59,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_config() -> Result<(), ConfigError> {
|
fn test_parse_config() -> Result<(), ConfigError> {
|
||||||
let file_name = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
let config_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("config.toml");
|
||||||
.join("config.toml")
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
.to_string();
|
|
||||||
env::set_var("SERVER__ADDRESS", "0.0.1.1");
|
env::set_var("SERVER__ADDRESS", "0.0.1.1");
|
||||||
let config = Config::parse(&file_name)?;
|
let config = Config::parse(&config_path)?;
|
||||||
assert_eq!("0.0.1.1", config.server.address);
|
assert_eq!("0.0.1.1", config.server.address);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
49
src/main.rs
49
src/main.rs
|
@ -1,39 +1,78 @@
|
||||||
use actix_web::client::ClientBuilder;
|
use actix_web::client::ClientBuilder;
|
||||||
use actix_web::middleware::Logger;
|
use actix_web::middleware::Logger;
|
||||||
use actix_web::{App, HttpServer};
|
use actix_web::{App, HttpServer};
|
||||||
|
use hotwatch::{Event, Hotwatch};
|
||||||
use rustypaste::config::Config;
|
use rustypaste::config::Config;
|
||||||
use rustypaste::paste::PasteType;
|
use rustypaste::paste::PasteType;
|
||||||
use rustypaste::server;
|
use rustypaste::server;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Result as IoResult;
|
use std::io::Result as IoResult;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> IoResult<()> {
|
async fn main() -> IoResult<()> {
|
||||||
dotenv::dotenv().ok();
|
// Initialize logger.
|
||||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
let config = Config::parse(env::var("CONFIG").as_deref().unwrap_or("config"))
|
|
||||||
.expect("failed to parse config");
|
// Parse configuration.
|
||||||
let server_config = config.server.clone();
|
dotenv::dotenv().ok();
|
||||||
|
let config_path =
|
||||||
|
PathBuf::from(env::var("CONFIG").unwrap_or_else(|_| String::from("config.toml")));
|
||||||
|
let config = Arc::new(Mutex::new(
|
||||||
|
Config::parse(&config_path).expect("failed to parse config"),
|
||||||
|
));
|
||||||
|
let cloned_config = Arc::clone(&config);
|
||||||
|
let server_config = config.lock().expect("cannot acquire config").server.clone();
|
||||||
|
|
||||||
|
// Create necessary directories.
|
||||||
fs::create_dir_all(&server_config.upload_path)?;
|
fs::create_dir_all(&server_config.upload_path)?;
|
||||||
for paste_type in &[PasteType::Url, PasteType::Oneshot] {
|
for paste_type in &[PasteType::Url, PasteType::Oneshot] {
|
||||||
fs::create_dir_all(paste_type.get_path(&server_config.upload_path))?;
|
fs::create_dir_all(paste_type.get_path(&server_config.upload_path))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up a watcher for the configuration file changes.
|
||||||
|
let mut hotwatch = Hotwatch::new_with_custom_delay(Duration::from_secs(1))
|
||||||
|
.expect("failed to initialize configuration file watcher");
|
||||||
|
|
||||||
|
// Hot-reload the configuration file.
|
||||||
|
hotwatch
|
||||||
|
.watch(&config_path, move |event: Event| {
|
||||||
|
if let Event::Write(path) = event {
|
||||||
|
match Config::parse(&path) {
|
||||||
|
Ok(config) => {
|
||||||
|
*cloned_config.lock().expect("cannot acquire config") = config;
|
||||||
|
log::info!("Configuration has been updated.");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to update configuration: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|_| panic!("failed to watch {:?}", config_path));
|
||||||
|
|
||||||
|
// Create a HTTP server.
|
||||||
let mut http_server = HttpServer::new(move || {
|
let mut http_server = HttpServer::new(move || {
|
||||||
let http_client = ClientBuilder::default()
|
let http_client = ClientBuilder::default()
|
||||||
.timeout(Duration::from_secs(30))
|
.timeout(Duration::from_secs(30))
|
||||||
.disable_redirects()
|
.disable_redirects()
|
||||||
.finish();
|
.finish();
|
||||||
App::new()
|
App::new()
|
||||||
.data(config.clone())
|
.data(Arc::clone(&config))
|
||||||
.data(http_client)
|
.data(http_client)
|
||||||
.wrap(Logger::default())
|
.wrap(Logger::default())
|
||||||
.configure(server::configure_routes)
|
.configure(server::configure_routes)
|
||||||
})
|
})
|
||||||
.bind(server_config.address)?;
|
.bind(server_config.address)?;
|
||||||
|
|
||||||
|
// Set worker count for the server.
|
||||||
if let Some(workers) = server_config.workers {
|
if let Some(workers) = server_config.workers {
|
||||||
http_server = http_server.workers(workers);
|
http_server = http_server.workers(workers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run the server.
|
||||||
http_server.run().await
|
http_server.run().await
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ use futures_util::stream::StreamExt;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
/// Shows the landing page.
|
/// Shows the landing page.
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
|
@ -81,13 +82,14 @@ async fn upload(
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
mut payload: Multipart,
|
mut payload: Multipart,
|
||||||
client: web::Data<Client>,
|
client: web::Data<Client>,
|
||||||
config: web::Data<Config>,
|
config: web::Data<Arc<Mutex<Config>>>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let connection = request.connection_info();
|
let connection = request.connection_info();
|
||||||
let host = connection.remote_addr().unwrap_or("unknown host");
|
let host = connection.remote_addr().unwrap_or("unknown host");
|
||||||
auth::check(host, request.headers(), env::var("AUTH_TOKEN").ok())?;
|
auth::check(host, request.headers(), env::var("AUTH_TOKEN").ok())?;
|
||||||
let expiry_date = header::parse_expiry_date(request.headers())?;
|
let expiry_date = header::parse_expiry_date(request.headers())?;
|
||||||
let mut urls: Vec<String> = Vec::new();
|
let mut urls: Vec<String> = Vec::new();
|
||||||
|
let config = config.lock().expect("cannot acquire config");
|
||||||
while let Some(item) = payload.next().await {
|
while let Some(item) = payload.next().await {
|
||||||
let mut field = item?;
|
let mut field = item?;
|
||||||
let content = ContentDisposition::try_from(field.content_disposition())?;
|
let content = ContentDisposition::try_from(field.content_disposition())?;
|
||||||
|
@ -96,7 +98,7 @@ async fn upload(
|
||||||
while let Some(chunk) = field.next().await {
|
while let Some(chunk) = field.next().await {
|
||||||
bytes.append(&mut chunk?.to_vec());
|
bytes.append(&mut chunk?.to_vec());
|
||||||
if bytes.len() as u128 > config.server.max_content_length.get_bytes() {
|
if bytes.len() as u128 > config.server.max_content_length.get_bytes() {
|
||||||
log::warn!("upload rejected for {}", host);
|
log::warn!("Upload rejected for {}", host);
|
||||||
return Err(error::ErrorPayloadTooLarge("upload limit exceeded"));
|
return Err(error::ErrorPayloadTooLarge("upload limit exceeded"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue