api & web unix sockets working

This commit is contained in:
Tom Mombourquette 2022-02-25 15:08:14 -04:00
parent 54e02b4c3b
commit 972426cee1
7 changed files with 164 additions and 59 deletions

15
Cargo.lock generated
View file

@ -711,6 +711,7 @@ dependencies = [
"http-range", "http-range",
"httpdate 0.3.2", "httpdate 0.3.2",
"hyper", "hyper",
"hyperlocal",
"idna", "idna",
"log", "log",
"md-5", "md-5",
@ -934,6 +935,7 @@ dependencies = [
"garage_util 0.6.0", "garage_util 0.6.0",
"http", "http",
"hyper", "hyper",
"hyperlocal",
"log", "log",
"percent-encoding", "percent-encoding",
] ]
@ -1156,6 +1158,19 @@ dependencies = [
"webpki", "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]] [[package]]
name = "idna" name = "idna"
version = "0.2.3" version = "0.2.3"

View file

@ -1026,6 +1026,7 @@ in
http_range = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http-range."0.1.4" { inherit profileName; }; 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; }; 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; }; 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; }; 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; }; 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; }; 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 { "registry+https://github.com/rust-lang/crates.io-index".idna."0.2.3" = overridableMkRustCrate (profileName: rec {
name = "idna"; name = "idna";
version = "0.2.3"; version = "0.2.3";
@ -2334,7 +2354,7 @@ in
]; ];
dependencies = { 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 == "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; }; ${ 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; }; 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; }; ${ 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 = { dependencies = {
bitflags = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bitflags."1.3.2" { inherit profileName; }; 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" 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; }; ${ 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"; }; static_init_macro = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".static_init_macro."1.0.2" { profileName = "__noProfile"; };

View file

@ -1,12 +1,12 @@
[package] [package]
name = "garage_api"
version = "0.6.0"
authors = ["Alex Auvolat <alex@adnab.me>"] authors = ["Alex Auvolat <alex@adnab.me>"]
description = "S3 API server crate for the Garage object store"
edition = "2018" edition = "2018"
license = "AGPL-3.0" license = "AGPL-3.0"
description = "S3 API server crate for the Garage object store" name = "garage_api"
repository = "https://git.deuxfleurs.fr/Deuxfleurs/garage"
readme = "../../README.md" readme = "../../README.md"
repository = "https://git.deuxfleurs.fr/Deuxfleurs/garage"
version = "0.6.0"
[lib] [lib]
path = "lib.rs" path = "lib.rs"
@ -38,14 +38,15 @@ tokio = { version = "1.0", default-features = false, features = ["rt", "rt-multi
form_urlencoded = "1.0.0" form_urlencoded = "1.0.0"
http = "0.2" http = "0.2"
httpdate = "0.3"
http-range = "0.1" http-range = "0.1"
httpdate = "0.3"
hyper = {version = "0.14", features = ["server", "http1", "runtime", "tcp", "stream"]} hyper = {version = "0.14", features = ["server", "http1", "runtime", "tcp", "stream"]}
hyperlocal = "0.8"
multer = "2.0" multer = "2.0"
percent-encoding = "2.1.0" percent-encoding = "2.1.0"
quick-xml = {version = "0.21", features = ["serialize"]}
roxmltree = "0.14" roxmltree = "0.14"
serde = {version = "1.0", features = ["derive"]} serde = {version = "1.0", features = ["derive"]}
serde_bytes = "0.11" serde_bytes = "0.11"
serde_json = "1.0" serde_json = "1.0"
quick-xml = { version = "0.21", features = [ "serialize" ] }
url = "2.1" url = "2.1"

View file

@ -1,5 +1,5 @@
use std::net::SocketAddr;
use std::sync::Arc; use std::sync::Arc;
use std::{fs, path::Path};
use futures::future::Future; use futures::future::Future;
use hyper::header; use hyper::header;
@ -7,6 +7,8 @@ use hyper::server::conn::AddrStream;
use hyper::service::{make_service_fn, service_fn}; use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Method, Request, Response, Server}; use hyper::{Body, Method, Request, Response, Server};
use hyperlocal::UnixServerExt;
use garage_util::data::*; use garage_util::data::*;
use garage_util::error::Error as GarageError; use garage_util::error::Error as GarageError;
@ -35,32 +37,57 @@ pub async fn run_api_server(
garage: Arc<Garage>, garage: Arc<Garage>,
shutdown_signal: impl Future<Output = ()>, shutdown_signal: impl Future<Output = ()>,
) -> Result<(), GarageError> { ) -> Result<(), GarageError> {
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 addr = &garage.config.s3_api.api_bind_addr;
let service = make_service_fn(|conn: &AddrStream| { let service = make_service_fn(|conn: &AddrStream| {
let garage = garage.clone(); let garage = garage.clone();
let client_addr = conn.remote_addr(); let client_addr = conn.remote_addr().to_string();
async move { async move {
Ok::<_, GarageError>(service_fn(move |req: Request<Body>| { Ok::<_, GarageError>(service_fn(move |req: Request<Body>| {
let garage = garage.clone(); let garage = garage.clone();
handler(garage, req, client_addr) handler(garage, req, client_addr.clone())
})) }))
} }
}); });
let server = Server::bind(addr).serve(service); let server = Server::bind(addr).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);
info!("API server listening on http://{}", addr);
graceful.await?; graceful.await?;
Ok(()) 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<Body>| {
let garage = garage.clone();
handler(garage, req, client_addr.clone())
}))
}
});
let listener = Server::bind_unix(path)?;
let server = listener.serve(service);
let graceful = server.with_graceful_shutdown(shutdown_signal);
info!("API server listening on {}", socket_path);
graceful.await?;
Ok(())
}
} }
async fn handler( async fn handler(
garage: Arc<Garage>, garage: Arc<Garage>,
req: Request<Body>, req: Request<Body>,
addr: SocketAddr, addr: String,
) -> Result<Response<Body>, GarageError> { ) -> Result<Response<Body>, GarageError> {
let uri = req.uri().clone(); let uri = req.uri().clone();
info!("{} {} {}", addr, req.method(), uri); info!("{} {} {}", addr, req.method(), uri);

View file

@ -37,6 +37,10 @@ pub struct Config {
)] )]
pub compression_level: Option<i32>, pub compression_level: Option<i32>,
/// Path to unix socket
#[serde(default = "default_unix_socket")]
pub rpc_unix_socket: String,
/// RPC secret key: 32 bytes hex encoded /// RPC secret key: 32 bytes hex encoded
pub rpc_secret: String, pub rpc_secret: String,
@ -71,6 +75,9 @@ pub struct Config {
/// Configuration for S3 api /// Configuration for S3 api
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct ApiConfig { 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 /// Address and port to bind for api serving
pub api_bind_addr: SocketAddr, pub api_bind_addr: SocketAddr,
/// S3 region to use /// S3 region to use
@ -83,6 +90,9 @@ pub struct ApiConfig {
/// Configuration for serving files as normal web server /// Configuration for serving files as normal web server
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct WebConfig { 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 /// Address and port to bind for web serving
pub bind_addr: SocketAddr, pub bind_addr: SocketAddr,
/// Suffix to remove from domain name to find bucket /// 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 { fn default_block_size() -> usize {
1048576 1048576
} }
fn default_unix_socket() -> String {
String::new()
}
/// Read and parse configuration /// Read and parse configuration
pub fn read_config(config_file: PathBuf) -> Result<Config, Error> { pub fn read_config(config_file: PathBuf) -> Result<Config, Error> {

View file

@ -1,12 +1,12 @@
[package] [package]
name = "garage_web"
version = "0.6.0"
authors = ["Alex Auvolat <alex@adnab.me>", "Quentin Dufour <quentin@dufour.io>"] authors = ["Alex Auvolat <alex@adnab.me>", "Quentin Dufour <quentin@dufour.io>"]
description = "S3-like website endpoint crate for the Garage object store"
edition = "2018" edition = "2018"
license = "AGPL-3.0" license = "AGPL-3.0"
description = "S3-like website endpoint crate for the Garage object store" name = "garage_web"
repository = "https://git.deuxfleurs.fr/Deuxfleurs/garage"
readme = "../../README.md" readme = "../../README.md"
repository = "https://git.deuxfleurs.fr/Deuxfleurs/garage"
version = "0.6.0"
[lib] [lib]
path = "lib.rs" path = "lib.rs"
@ -16,8 +16,8 @@ path = "lib.rs"
[dependencies] [dependencies]
garage_api = {version = "0.6.0", path = "../api"} garage_api = {version = "0.6.0", path = "../api"}
garage_model = {version = "0.6.0", path = "../model"} 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_table = {version = "0.6.0", path = "../table"}
garage_util = {version = "0.6.0", path = "../util"}
err-derive = "0.3" err-derive = "0.3"
log = "0.4" log = "0.4"
@ -27,3 +27,4 @@ futures = "0.3"
http = "0.2" 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"

View file

@ -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; use futures::future::Future;
@ -9,6 +9,8 @@ use hyper::{
Body, Method, Request, Response, Server, Body, Method, Request, Response, Server,
}; };
use hyperlocal::UnixServerExt;
use crate::error::*; use crate::error::*;
use garage_api::error::{Error as ApiError, OkOrBadRequest, OkOrInternalError}; use garage_api::error::{Error as ApiError, OkOrBadRequest, OkOrInternalError};
@ -26,31 +28,57 @@ pub async fn run_web_server(
garage: Arc<Garage>, garage: Arc<Garage>,
shutdown_signal: impl Future<Output = ()>, shutdown_signal: impl Future<Output = ()>,
) -> Result<(), GarageError> { ) -> Result<(), GarageError> {
let socket_path = &garage.config.s3_web.web_unix_socket;
if socket_path.is_empty() {
let addr = &garage.config.s3_web.bind_addr; let addr = &garage.config.s3_web.bind_addr;
let service = make_service_fn(|conn: &AddrStream| { let service = make_service_fn(|conn: &AddrStream| {
let garage = garage.clone(); let garage = garage.clone();
let client_addr = conn.remote_addr(); let client_addr = conn.remote_addr().to_string();
async move { async move {
Ok::<_, Error>(service_fn(move |req: Request<Body>| { Ok::<_, GarageError>(service_fn(move |req: Request<Body>| {
let garage = garage.clone(); let garage = garage.clone();
handle_request(garage, req, client_addr) handler(garage, req, client_addr.clone())
})) }))
} }
}); });
let server = Server::bind(addr).serve(service); let server = Server::bind(addr).serve(service);
let graceful = server.with_graceful_shutdown(shutdown_signal); let graceful = server.with_graceful_shutdown(shutdown_signal);
info!("Web server listening on http://{}", addr);
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<Body>| {
let garage = garage.clone();
handler(garage, req, client_addr.clone())
}))
}
});
let listener = Server::bind_unix(path)?;
let server = listener.serve(service);
let graceful = server.with_graceful_shutdown(shutdown_signal);
info!("Web server listening on {}", socket_path);
graceful.await?; graceful.await?;
Ok(()) Ok(())
} }
}
async fn handle_request( async fn handler(
garage: Arc<Garage>, garage: Arc<Garage>,
req: Request<Body>, req: Request<Body>,
addr: SocketAddr, addr: String,
) -> Result<Response<Body>, Infallible> { ) -> Result<Response<Body>, Infallible> {
info!("{} {} {}", addr, req.method(), req.uri()); info!("{} {} {}", addr, req.method(), req.uri());
match serve_file(garage, &req).await { match serve_file(garage, &req).await {