mirror of
https://github.com/amigan/rustypaste-pretty.git
synced 2024-11-21 11:59:48 -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",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "flate2"
|
||||
version = "1.0.20"
|
||||
|
@ -680,6 +692,25 @@ dependencies = [
|
|||
"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]]
|
||||
name = "fuchsia-zircon"
|
||||
version = "0.3.3"
|
||||
|
@ -880,6 +911,16 @@ dependencies = [
|
|||
"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]]
|
||||
name = "http"
|
||||
version = "0.2.4"
|
||||
|
@ -930,6 +971,26 @@ version = "0.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "instant"
|
||||
version = "0.1.10"
|
||||
|
@ -1029,6 +1090,12 @@ version = "1.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "lexical-core"
|
||||
version = "0.7.6"
|
||||
|
@ -1144,6 +1211,18 @@ dependencies = [
|
|||
"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]]
|
||||
name = "mio-uds"
|
||||
version = "0.6.8"
|
||||
|
@ -1199,6 +1278,24 @@ dependencies = [
|
|||
"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]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.0"
|
||||
|
@ -1549,6 +1646,7 @@ dependencies = [
|
|||
"env_logger",
|
||||
"futures-util",
|
||||
"glob",
|
||||
"hotwatch",
|
||||
"humantime",
|
||||
"infer",
|
||||
"lazy-regex",
|
||||
|
@ -1569,6 +1667,15 @@ version = "1.0.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
|
@ -2166,6 +2273,17 @@ version = "0.9.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
|
|
|
@ -31,6 +31,7 @@ lazy-regex = "2.2.1"
|
|||
humantime = "2.1.0"
|
||||
glob = "0.3.0"
|
||||
ring = "0.16.20"
|
||||
hotwatch = "0.4.5"
|
||||
|
||||
[dependencies.config]
|
||||
version = "0.11.0"
|
||||
|
|
|
@ -14,7 +14,7 @@ some text
|
|||
|
||||
## Features
|
||||
|
||||
- File upload & URL shortening
|
||||
- File upload & URL shortening & upload from URL
|
||||
- supports basic HTTP authentication
|
||||
- random file names (optional)
|
||||
- pet name (e.g. `capital-mosquito.txt`)
|
||||
|
@ -26,6 +26,8 @@ some text
|
|||
- no duplicate uploads (optional)
|
||||
- Single binary
|
||||
- [binary releases](https://github.com/orhun/rustypaste/releases)
|
||||
- Simple configuration
|
||||
- supports hot reloading
|
||||
- Easy to deploy
|
||||
- [docker images](https://hub.docker.com/r/orhunp/rustypaste)
|
||||
- No database
|
||||
|
@ -184,7 +186,7 @@ http {
|
|||
|
||||
### Roadmap
|
||||
|
||||
- Hot reload the configuration file
|
||||
_Nothing here yet! 🎉_
|
||||
|
||||
### Contributing
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::mime::MimeMatcher;
|
|||
use crate::random::RandomURLConfig;
|
||||
use byte_unit::Byte;
|
||||
use config::{self, ConfigError};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Configuration values.
|
||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
||||
|
@ -43,10 +43,10 @@ pub struct PasteConfig {
|
|||
|
||||
impl Config {
|
||||
/// 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();
|
||||
config
|
||||
.merge(config::File::with_name(file_name))?
|
||||
.merge(config::File::from(path))?
|
||||
.merge(config::Environment::new().separator("__"))?;
|
||||
config.try_into()
|
||||
}
|
||||
|
@ -59,13 +59,9 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_parse_config() -> Result<(), ConfigError> {
|
||||
let file_name = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("config.toml")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let config_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("config.toml");
|
||||
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);
|
||||
Ok(())
|
||||
}
|
||||
|
|
49
src/main.rs
49
src/main.rs
|
@ -1,39 +1,78 @@
|
|||
use actix_web::client::ClientBuilder;
|
||||
use actix_web::middleware::Logger;
|
||||
use actix_web::{App, HttpServer};
|
||||
use hotwatch::{Event, Hotwatch};
|
||||
use rustypaste::config::Config;
|
||||
use rustypaste::paste::PasteType;
|
||||
use rustypaste::server;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::Result as IoResult;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> IoResult<()> {
|
||||
dotenv::dotenv().ok();
|
||||
// Initialize logger.
|
||||
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");
|
||||
let server_config = config.server.clone();
|
||||
|
||||
// Parse configuration.
|
||||
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)?;
|
||||
for paste_type in &[PasteType::Url, PasteType::Oneshot] {
|
||||
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 http_client = ClientBuilder::default()
|
||||
.timeout(Duration::from_secs(30))
|
||||
.disable_redirects()
|
||||
.finish();
|
||||
App::new()
|
||||
.data(config.clone())
|
||||
.data(Arc::clone(&config))
|
||||
.data(http_client)
|
||||
.wrap(Logger::default())
|
||||
.configure(server::configure_routes)
|
||||
})
|
||||
.bind(server_config.address)?;
|
||||
|
||||
// Set worker count for the server.
|
||||
if let Some(workers) = server_config.workers {
|
||||
http_server = http_server.workers(workers);
|
||||
}
|
||||
|
||||
// Run the server.
|
||||
http_server.run().await
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ use futures_util::stream::StreamExt;
|
|||
use std::convert::TryFrom;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// Shows the landing page.
|
||||
#[get("/")]
|
||||
|
@ -81,13 +82,14 @@ async fn upload(
|
|||
request: HttpRequest,
|
||||
mut payload: Multipart,
|
||||
client: web::Data<Client>,
|
||||
config: web::Data<Config>,
|
||||
config: web::Data<Arc<Mutex<Config>>>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let connection = request.connection_info();
|
||||
let host = connection.remote_addr().unwrap_or("unknown host");
|
||||
auth::check(host, request.headers(), env::var("AUTH_TOKEN").ok())?;
|
||||
let expiry_date = header::parse_expiry_date(request.headers())?;
|
||||
let mut urls: Vec<String> = Vec::new();
|
||||
let config = config.lock().expect("cannot acquire config");
|
||||
while let Some(item) = payload.next().await {
|
||||
let mut field = item?;
|
||||
let content = ContentDisposition::try_from(field.content_disposition())?;
|
||||
|
@ -96,7 +98,7 @@ async fn upload(
|
|||
while let Some(chunk) = field.next().await {
|
||||
bytes.append(&mut chunk?.to_vec());
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue