diff --git a/Cargo.lock b/Cargo.lock index 84ab24a..0a8c7b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -711,6 +711,7 @@ dependencies = [ "http-range", "httpdate 0.3.2", "hyper", + "hyperlocal", "idna", "log", "md-5", @@ -934,6 +935,7 @@ dependencies = [ "garage_util 0.6.0", "http", "hyper", + "hyperlocal", "log", "percent-encoding", ] @@ -1156,6 +1158,19 @@ dependencies = [ "webpki", ] +[[package]] +name = "hyperlocal" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c" +dependencies = [ + "futures-util", + "hex", + "hyper", + "pin-project", + "tokio", +] + [[package]] name = "idna" version = "0.2.3" diff --git a/Cargo.nix b/Cargo.nix index 8855e19..95b27b4 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -1026,6 +1026,7 @@ in http_range = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http-range."0.1.4" { inherit profileName; }; httpdate = rustPackages."registry+https://github.com/rust-lang/crates.io-index".httpdate."0.3.2" { inherit profileName; }; hyper = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.13" { inherit profileName; }; + hyperlocal = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyperlocal."0.8.0" { inherit profileName; }; idna = rustPackages."registry+https://github.com/rust-lang/crates.io-index".idna."0.2.3" { inherit profileName; }; log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.14" { inherit profileName; }; md5 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".md-5."0.9.1" { inherit profileName; }; @@ -1551,6 +1552,25 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".hyperlocal."0.8.0" = overridableMkRustCrate (profileName: rec { + name = "hyperlocal"; + version = "0.8.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c"; }; + features = builtins.concatLists [ + [ "client" ] + [ "default" ] + [ "server" ] + ]; + dependencies = { + futures_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.17" { inherit profileName; }; + hex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; + hyper = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.13" { inherit profileName; }; + pin_project = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project."1.0.8" { inherit profileName; }; + tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.12.0" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".idna."0.2.3" = overridableMkRustCrate (profileName: rec { name = "idna"; version = "0.2.3"; @@ -2334,7 +2354,7 @@ in ]; dependencies = { ${ if hostPlatform.parsed.kernel.name == "android" || hostPlatform.parsed.kernel.name == "linux" then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.115" { inherit profileName; }; - ${ if hostPlatform.parsed.kernel.name == "dragonfly" || hostPlatform.parsed.kernel.name == "freebsd" || hostPlatform.parsed.kernel.name == "illumos" || hostPlatform.parsed.kernel.name == "netbsd" || hostPlatform.parsed.kernel.name == "openbsd" || hostPlatform.parsed.kernel.name == "solaris" || hostPlatform.parsed.kernel.name == "android" || hostPlatform.parsed.kernel.name == "linux" then "once_cell" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".once_cell."1.8.0" { inherit profileName; }; + ${ if hostPlatform.parsed.kernel.name == "android" || hostPlatform.parsed.kernel.name == "linux" || hostPlatform.parsed.kernel.name == "dragonfly" || hostPlatform.parsed.kernel.name == "freebsd" || hostPlatform.parsed.kernel.name == "illumos" || hostPlatform.parsed.kernel.name == "netbsd" || hostPlatform.parsed.kernel.name == "openbsd" || hostPlatform.parsed.kernel.name == "solaris" then "once_cell" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".once_cell."1.8.0" { inherit profileName; }; ${ if hostPlatform.parsed.cpu.name == "i686" || hostPlatform.parsed.cpu.name == "x86_64" || (hostPlatform.parsed.cpu.name == "aarch64" || hostPlatform.parsed.cpu.name == "armv6l" || hostPlatform.parsed.cpu.name == "armv7l") && (hostPlatform.parsed.kernel.name == "android" || hostPlatform.parsed.kernel.name == "fuchsia" || hostPlatform.parsed.kernel.name == "linux") then "spin" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".spin."0.5.2" { inherit profileName; }; untrusted = rustPackages."registry+https://github.com/rust-lang/crates.io-index".untrusted."0.7.1" { inherit profileName; }; ${ if hostPlatform.parsed.cpu.name == "wasm32" && hostPlatform.parsed.vendor.name == "unknown" && hostPlatform.parsed.kernel.name == "unknown" && hostPlatform.parsed.abi.name == "" then "web_sys" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".web-sys."0.3.56" { inherit profileName; }; @@ -2704,7 +2724,7 @@ in ]; dependencies = { bitflags = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bitflags."1.3.2" { inherit profileName; }; - ${ if hostPlatform.parsed.kernel.name == "linux" || hostPlatform.parsed.kernel.name == "android" then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.115" { inherit profileName; }; + ${ if hostPlatform.parsed.kernel.name == "android" || hostPlatform.parsed.kernel.name == "linux" then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.115" { inherit profileName; }; ${ if !(hostPlatform.parsed.kernel.name == "linux" || hostPlatform.parsed.kernel.name == "android") then "parking_lot" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".parking_lot."0.11.2" { inherit profileName; }; ${ if !(hostPlatform.parsed.kernel.name == "linux" || hostPlatform.parsed.kernel.name == "android") then "parking_lot_core" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".parking_lot_core."0.8.5" { inherit profileName; }; static_init_macro = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".static_init_macro."1.0.2" { profileName = "__noProfile"; }; diff --git a/src/api/Cargo.toml b/src/api/Cargo.toml index cc9635b..ad1d719 100644 --- a/src/api/Cargo.toml +++ b/src/api/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "garage_api" -version = "0.6.0" authors = ["Alex Auvolat "] +description = "S3 API server crate for the Garage object store" edition = "2018" license = "AGPL-3.0" -description = "S3 API server crate for the Garage object store" -repository = "https://git.deuxfleurs.fr/Deuxfleurs/garage" +name = "garage_api" readme = "../../README.md" +repository = "https://git.deuxfleurs.fr/Deuxfleurs/garage" +version = "0.6.0" [lib] path = "lib.rs" @@ -14,9 +14,9 @@ path = "lib.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -garage_model = { version = "0.6.0", path = "../model" } -garage_table = { version = "0.6.0", path = "../table" } -garage_util = { version = "0.6.0", path = "../util" } +garage_model = {version = "0.6.0", path = "../model"} +garage_table = {version = "0.6.0", path = "../table"} +garage_util = {version = "0.6.0", path = "../util"} base64 = "0.13" bytes = "1.0" @@ -34,18 +34,19 @@ sha2 = "0.9" futures = "0.3" futures-util = "0.3" pin-project = "1.0" -tokio = { version = "1.0", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "time", "macros", "sync", "signal", "fs"] } +tokio = {version = "1.0", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "time", "macros", "sync", "signal", "fs"]} form_urlencoded = "1.0.0" http = "0.2" -httpdate = "0.3" http-range = "0.1" -hyper = { version = "0.14", features = ["server", "http1", "runtime", "tcp", "stream"] } +httpdate = "0.3" +hyper = {version = "0.14", features = ["server", "http1", "runtime", "tcp", "stream"]} +hyperlocal = "0.8" multer = "2.0" percent-encoding = "2.1.0" +quick-xml = {version = "0.21", features = ["serialize"]} roxmltree = "0.14" -serde = { version = "1.0", features = ["derive"] } +serde = {version = "1.0", features = ["derive"]} serde_bytes = "0.11" serde_json = "1.0" -quick-xml = { version = "0.21", features = [ "serialize" ] } url = "2.1" diff --git a/src/api/api_server.rs b/src/api/api_server.rs index 77587de..2c374bb 100644 --- a/src/api/api_server.rs +++ b/src/api/api_server.rs @@ -1,5 +1,5 @@ -use std::net::SocketAddr; use std::sync::Arc; +use std::{fs, path::Path}; use futures::future::Future; use hyper::header; @@ -7,6 +7,8 @@ use hyper::server::conn::AddrStream; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Method, Request, Response, Server}; +use hyperlocal::UnixServerExt; + use garage_util::data::*; use garage_util::error::Error as GarageError; @@ -35,32 +37,57 @@ pub async fn run_api_server( garage: Arc, shutdown_signal: impl Future, ) -> Result<(), GarageError> { - let addr = &garage.config.s3_api.api_bind_addr; + let socket_path = &garage.config.s3_api.api_unix_socket; + if socket_path.is_empty() { + let addr = &garage.config.s3_api.api_bind_addr; - let service = make_service_fn(|conn: &AddrStream| { - let garage = garage.clone(); - let client_addr = conn.remote_addr(); - async move { - Ok::<_, GarageError>(service_fn(move |req: Request| { - let garage = garage.clone(); - handler(garage, req, client_addr) - })) + let service = make_service_fn(|conn: &AddrStream| { + let garage = garage.clone(); + let client_addr = conn.remote_addr().to_string(); + async move { + Ok::<_, GarageError>(service_fn(move |req: Request| { + let garage = garage.clone(); + handler(garage, req, client_addr.clone()) + })) + } + }); + + let server = Server::bind(addr).serve(service); + let graceful = server.with_graceful_shutdown(shutdown_signal); + + info!("API server listening on http://{}", addr); + graceful.await?; + Ok(()) + } else { + let path = Path::new(socket_path); + if path.exists() { + fs::remove_file(path)?; } - }); + let service = make_service_fn(|_conn| { + let garage = garage.clone(); + let client_addr = String::from("unix"); + async move { + Ok::<_, GarageError>(service_fn(move |req: Request| { + let garage = garage.clone(); + handler(garage, req, client_addr.clone()) + })) + } + }); - let server = Server::bind(addr).serve(service); + let listener = Server::bind_unix(path)?; + let server = listener.serve(service); + let graceful = server.with_graceful_shutdown(shutdown_signal); - let graceful = server.with_graceful_shutdown(shutdown_signal); - info!("API server listening on http://{}", addr); - - graceful.await?; - Ok(()) + info!("API server listening on {}", socket_path); + graceful.await?; + Ok(()) + } } async fn handler( garage: Arc, req: Request, - addr: SocketAddr, + addr: String, ) -> Result, GarageError> { let uri = req.uri().clone(); info!("{} {} {}", addr, req.method(), uri); diff --git a/src/util/config.rs b/src/util/config.rs index f1f4b06..8b5e831 100644 --- a/src/util/config.rs +++ b/src/util/config.rs @@ -37,6 +37,10 @@ pub struct Config { )] pub compression_level: Option, + /// Path to unix socket + #[serde(default = "default_unix_socket")] + pub rpc_unix_socket: String, + /// RPC secret key: 32 bytes hex encoded pub rpc_secret: String, @@ -71,6 +75,9 @@ pub struct Config { /// Configuration for S3 api #[derive(Deserialize, Debug, Clone)] pub struct ApiConfig { + /// Path to unix socket + #[serde(default = "default_unix_socket")] + pub api_unix_socket: String, /// Address and port to bind for api serving pub api_bind_addr: SocketAddr, /// S3 region to use @@ -83,6 +90,9 @@ pub struct ApiConfig { /// Configuration for serving files as normal web server #[derive(Deserialize, Debug, Clone)] pub struct WebConfig { + /// Path to unix socket + #[serde(default = "default_unix_socket")] + pub web_unix_socket: String, /// Address and port to bind for web serving pub bind_addr: SocketAddr, /// Suffix to remove from domain name to find bucket @@ -98,6 +108,9 @@ fn default_sled_flush_every_ms() -> u64 { fn default_block_size() -> usize { 1048576 } +fn default_unix_socket() -> String { + String::new() +} /// Read and parse configuration pub fn read_config(config_file: PathBuf) -> Result { diff --git a/src/web/Cargo.toml b/src/web/Cargo.toml index 54211f5..4faa527 100644 --- a/src/web/Cargo.toml +++ b/src/web/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "garage_web" -version = "0.6.0" authors = ["Alex Auvolat ", "Quentin Dufour "] +description = "S3-like website endpoint crate for the Garage object store" edition = "2018" license = "AGPL-3.0" -description = "S3-like website endpoint crate for the Garage object store" -repository = "https://git.deuxfleurs.fr/Deuxfleurs/garage" +name = "garage_web" readme = "../../README.md" +repository = "https://git.deuxfleurs.fr/Deuxfleurs/garage" +version = "0.6.0" [lib] path = "lib.rs" @@ -14,10 +14,10 @@ path = "lib.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -garage_api = { version = "0.6.0", path = "../api" } -garage_model = { version = "0.6.0", path = "../model" } -garage_util = { version = "0.6.0", path = "../util" } -garage_table = { version = "0.6.0", path = "../table" } +garage_api = {version = "0.6.0", path = "../api"} +garage_model = {version = "0.6.0", path = "../model"} +garage_table = {version = "0.6.0", path = "../table"} +garage_util = {version = "0.6.0", path = "../util"} err-derive = "0.3" log = "0.4" @@ -26,4 +26,5 @@ percent-encoding = "2.1.0" futures = "0.3" http = "0.2" -hyper = { version = "0.14", features = ["server", "http1", "runtime", "tcp", "stream"] } +hyper = {version = "0.14", features = ["server", "http1", "runtime", "tcp", "stream"]} +hyperlocal = "0.8" diff --git a/src/web/web_server.rs b/src/web/web_server.rs index 6c7d7c3..8aaebac 100644 --- a/src/web/web_server.rs +++ b/src/web/web_server.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, convert::Infallible, net::SocketAddr, sync::Arc}; +use std::{borrow::Cow, convert::Infallible, fs, path::Path, sync::Arc}; use futures::future::Future; @@ -9,6 +9,8 @@ use hyper::{ Body, Method, Request, Response, Server, }; +use hyperlocal::UnixServerExt; + use crate::error::*; use garage_api::error::{Error as ApiError, OkOrBadRequest, OkOrInternalError}; @@ -26,31 +28,57 @@ pub async fn run_web_server( garage: Arc, shutdown_signal: impl Future, ) -> Result<(), GarageError> { - let addr = &garage.config.s3_web.bind_addr; + let socket_path = &garage.config.s3_web.web_unix_socket; + if socket_path.is_empty() { + let addr = &garage.config.s3_web.bind_addr; - let service = make_service_fn(|conn: &AddrStream| { - let garage = garage.clone(); - let client_addr = conn.remote_addr(); - async move { - Ok::<_, Error>(service_fn(move |req: Request| { - let garage = garage.clone(); - handle_request(garage, req, client_addr) - })) + let service = make_service_fn(|conn: &AddrStream| { + let garage = garage.clone(); + let client_addr = conn.remote_addr().to_string(); + async move { + Ok::<_, GarageError>(service_fn(move |req: Request| { + let garage = garage.clone(); + handler(garage, req, client_addr.clone()) + })) + } + }); + + let server = Server::bind(addr).serve(service); + let graceful = server.with_graceful_shutdown(shutdown_signal); + + info!("Web server listening on http://{}", addr); + graceful.await?; + Ok(()) + } else { + let path = Path::new(socket_path); + if path.exists() { + fs::remove_file(path)?; } - }); + let service = make_service_fn(|_conn| { + let garage = garage.clone(); + let client_addr = String::from("unix"); + async move { + Ok::<_, GarageError>(service_fn(move |req: Request| { + let garage = garage.clone(); + handler(garage, req, client_addr.clone()) + })) + } + }); - let server = Server::bind(addr).serve(service); - let graceful = server.with_graceful_shutdown(shutdown_signal); - info!("Web server listening on http://{}", addr); + let listener = Server::bind_unix(path)?; + let server = listener.serve(service); + let graceful = server.with_graceful_shutdown(shutdown_signal); - graceful.await?; - Ok(()) + info!("Web server listening on {}", socket_path); + graceful.await?; + Ok(()) + } } -async fn handle_request( +async fn handler( garage: Arc, req: Request, - addr: SocketAddr, + addr: String, ) -> Result, Infallible> { info!("{} {} {}", addr, req.method(), req.uri()); match serve_file(garage, &req).await {