Compare commits

..

10 Commits
main ... proxy

5 changed files with 497 additions and 18 deletions

319
Cargo.lock generated
View File

@ -192,6 +192,22 @@ dependencies = [
"version_check",
]
[[package]]
name = "core-foundation"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "cpufeatures"
version = "0.2.1"
@ -347,6 +363,31 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
dependencies = [
"matches",
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.18"
@ -578,6 +619,30 @@ dependencies = [
"want",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes",
"hyper",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "idna"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
dependencies = [
"matches",
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "indexmap"
version = "1.7.0"
@ -604,12 +669,27 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "ipnet"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "js-sys"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "jwt"
version = "0.14.0"
@ -694,6 +774,12 @@ dependencies = [
"regex-automata",
]
[[package]]
name = "matches"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "memchr"
version = "2.4.1"
@ -778,6 +864,24 @@ dependencies = [
"version_check",
]
[[package]]
name = "native-tls"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "ntapi"
version = "0.3.6"
@ -828,6 +932,39 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl"
version = "0.10.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-sys",
]
[[package]]
name = "openssl-probe"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
[[package]]
name = "openssl-sys"
version = "0.9.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7df13d165e607909b363a4757a6f133f8a818a74e9d3a98d09c6128e15fa4c73"
dependencies = [
"autocfg",
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "parking_lot"
version = "0.11.2"
@ -894,6 +1031,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f"
[[package]]
name = "ppv-lite86"
version = "0.2.15"
@ -1019,20 +1162,24 @@ name = "rb-gw"
version = "0.1.0"
dependencies = [
"base64",
"bytes",
"chrono",
"diesel",
"diesel_migrations",
"figment",
"futures",
"hmac",
"jwt",
"mimalloc",
"rand",
"rb",
"reqwest",
"rocket",
"rocket_sync_db_pools",
"rust-argon2",
"serde",
"sha2",
"tokio-util",
"uuid",
]
@ -1098,6 +1245,41 @@ dependencies = [
"winapi",
]
[[package]]
name = "reqwest"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66d2927ca2f685faf0fc620ac4834690d29e7abb153add10f5812eef20b5e280"
dependencies = [
"base64",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"http",
"http-body",
"hyper",
"hyper-tls",
"ipnet",
"js-sys",
"lazy_static",
"log",
"mime",
"native-tls",
"percent-encoding",
"pin-project-lite",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-native-tls",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"winreg",
]
[[package]]
name = "rocket"
version = "0.5.0-rc.1"
@ -1240,6 +1422,16 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "schannel"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
dependencies = [
"lazy_static",
"winapi",
]
[[package]]
name = "scheduled-thread-pool"
version = "0.2.5"
@ -1261,6 +1453,29 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "security-framework"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "semver"
version = "0.9.0"
@ -1298,15 +1513,27 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.71"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19"
checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.8.21"
@ -1468,9 +1695,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.81"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
dependencies = [
"proc-macro2",
"quote",
@ -1548,6 +1775,21 @@ dependencies = [
"syn",
]
[[package]]
name = "tinyvec"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.14.0"
@ -1578,6 +1820,16 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.8"
@ -1597,6 +1849,7 @@ checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0"
dependencies = [
"bytes",
"futures-core",
"futures-io",
"futures-sink",
"log",
"pin-project-lite",
@ -1726,12 +1979,39 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c"
[[package]]
name = "unicode-bidi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
[[package]]
name = "unicode-normalization"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "url"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
dependencies = [
"form_urlencoded",
"idna",
"matches",
"percent-encoding",
]
[[package]]
name = "uuid"
version = "0.8.2"
@ -1794,6 +2074,18 @@ dependencies = [
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.78"
@ -1823,6 +2115,16 @@ version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
[[package]]
name = "web-sys"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -1845,6 +2147,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "winreg"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
dependencies = [
"winapi",
]
[[package]]
name = "yaml-rust"
version = "0.4.5"

View File

@ -39,6 +39,10 @@ base64 = "0.13.0"
# Reading in configuration files
figment = { version = "*", features = [ "yaml" ] }
mimalloc = { version = "0.1.26", default_features = false }
reqwest = { version = "0.11.6", features = [ "stream" ] }
tokio-util = {version="*", features=[ "compat" ] }
bytes = "*"
futures = "0.3.18"
[profile.dev]
lto = "off"

View File

@ -21,7 +21,7 @@ debug:
refresh_token_expire: 60
services:
blog: "http://localhost:8002"
blog: "http://localhost:8001"
databases:
postgres_rb:

View File

@ -130,13 +130,14 @@ fn rocket() -> _
rocket
.mount(
"/v1/posts",
ProxyServer::from(format!("{}/v1/posts", services_conf.blog)),
"/api/v1/posts",
ProxyServer::from(&services_conf.blog, "/v1/posts"),
)
.mount(
"/v1/sections",
ProxyServer::from(format!("{}/v1/sections", services_conf.blog)),
"/api/v1/sections",
ProxyServer::from(&services_conf.blog, "/v1/sections"),
)
.mount("/yeet", ProxyServer::from("http://localhost:5000", "/"))
.manage(jwt_conf)
.manage(admin_conf)
.attach(AdHoc::try_on_ignite("Create admin user", create_admin_user))

View File

@ -1,37 +1,70 @@
//! This file is heavily inspired by the FileServer implementation in Rocket
use std::path::{Path, PathBuf};
use futures::stream::TryStreamExt;
use reqwest::{
header::{HeaderMap, HeaderName, HeaderValue},
Method as ReqMethod,
};
use rocket::{
http::Method,
response::Redirect,
http::uri::Segments,
http::ext::IntoOwned,
fs::Options,
data::ToByteUnit,
http::{Method, Status},
route::{Handler, Outcome},
Data, Request, Route,
};
use tokio_util::compat::FuturesAsyncReadCompatExt;
/// This proxy server implementation proxies requests according to prefixes in their URLs. It
/// strips the mounted prefix from the URL & replaces it with the configured prefix beforing
/// proxying the request. This allows mounting a subdirectory of a server on another subdirectory
/// on the proxy (e.g. /api/v1/posts routes to /v1/posts).
#[derive(Clone)]
pub struct ProxyServer
{
/// Base URL of the server to proxy to. This should include the protocol (http or https), as
/// this value is passed directly to reqwest.
root: String,
/// With what prefix the mount prefix should be replaced
prefix: PathBuf,
/// Rank of the generated routes
rank: isize,
/// Possible options; same as FileServer
options: Options,
}
impl ProxyServer
{
const DEFAULT_RANK: isize = 0;
pub fn new(root: String, rank: isize) -> Self
/// Creates a new ProxyServer object.
pub fn new<P: AsRef<Path>>(root: &str, prefix: P, rank: isize, options: Options) -> Self
{
ProxyServer { root, rank }
ProxyServer {
root: String::from(root),
prefix: prefix.as_ref().into(),
rank,
options,
}
}
pub fn from(root: String) -> Self
/// Convenience function to initialize a ProxyServer with the default rank & options.
pub fn from<P: AsRef<Path>>(root: &str, prefix: P) -> Self
{
Self::new(root, Self::DEFAULT_RANK)
Self::new(root, prefix, Self::DEFAULT_RANK, Options::NormalizeDirs)
}
}
impl Into<Vec<Route>> for ProxyServer
{
/// Converts the handler into a list of Route objects. This is used internally by Rocket to
/// mount the handler.
fn into(self) -> Vec<Route>
{
let mut routes: Vec<Route> = vec![];
let mut routes: Vec<Route> = Vec::new();
static METHODS: [Method; 5] = [
Method::Get,
@ -43,8 +76,15 @@ impl Into<Vec<Route>> for ProxyServer
for method in METHODS {
let mut route = Route::ranked(self.rank, method, "/<path..>", self.clone());
route.name =
Some(format!("ProxyServer: {} {}", method.as_str(), self.root.clone()).into());
route.name = Some(
format!(
"ProxyServer: {} {}{}",
method.as_str(),
self.root.clone(),
self.prefix.to_str().unwrap()
)
.into(),
);
routes.push(route);
}
@ -58,6 +98,129 @@ impl Handler for ProxyServer
{
async fn handle<'r>(&self, req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r>
{
todo!()
use rocket::http::uri::fmt::Path;
// ROCKET REQUEST -> REQWEST REQUEST
// We first parse the path & prepend it with the prefix. If we can't parse it, we return a
// BadRequest.
let allow_dotfiles = self.options.contains(Options::DotFiles);
let path = req.segments::<Segments<'_, Path>>(0..).ok()
.and_then(|segments| segments.to_path_buf(allow_dotfiles).ok())
.map(|path| self.prefix.join(path));
if path.is_none() {
return Outcome::Failure(Status::BadRequest);
}
let path = path.unwrap(); // Unwrap is safe because we check for is_none() beforehand
let path_str = path.to_str();
if path_str.is_none() {
return Outcome::Failure(Status::BadRequest);
}
// After confirming we can parse the path, we check if it needs to be normalized
if self.options.contains(Options::NormalizeDirs) && !req.uri().path().ends_with('/') {
let normal = req.uri().map_path(|p| format!("{}/", p))
.expect("adding a trailing slash to a known good path => valid path")
.into_owned();
return Outcome::from_or_forward(req, data, Redirect::permanent(normal));
}
// Finally, we create our new URL by combining the root, path & query
let path_str = path_str.unwrap();
let query_part = req
.uri()
.query()
.map_or(String::from(""), |s| format!("?{}", s));
let url = format!(
"{}{}{}",
self.root,
path_str,
query_part
);
// Here, we convert the Rocket headers into a HeaderMap that reqwest can use.
let mut headers = HeaderMap::new();
for header in req.headers().iter() {
let name = match HeaderName::from_lowercase(header.name.into_string().as_bytes()) {
Ok(val) => val,
Err(_) => return Outcome::Failure(Status::BadRequest),
};
let value = match HeaderValue::from_str(&header.value.into_owned()) {
Ok(val) => val,
Err(_) => return Outcome::Failure(Status::BadRequest),
};
headers.insert(name, value);
}
// Each method needs to be matched against the other's version
let method = match req.method() {
Method::Delete => ReqMethod::DELETE,
Method::Get => ReqMethod::GET,
Method::Patch => ReqMethod::PATCH,
Method::Post => ReqMethod::POST,
Method::Put => ReqMethod::PUT,
_ => todo!(), // This should never be reached as the proxy only mounts on these five routes
};
// And finally, the data
// TODO don't hard-code max request size here
static BODY_SIZE: i64 = 10;
// TODO remove unwrap
let body = data
.open(BODY_SIZE.mebibytes())
.into_bytes()
.await;
if body.is_err() {
return Outcome::Failure(Status::InternalServerError);
}
// TODO remove unwrap
// let body = reqwest::Body::wrap_stream(data);
let new_res = reqwest::Client::new()
.request(method, url)
.headers(headers)
.body(body.unwrap().to_vec())
.send()
.await;
if new_res.is_err() {
return Outcome::Failure(Status::InternalServerError);
}
let new_res = new_res.unwrap();
let mut res_headers = new_res.headers().clone();
let mut res_builder = rocket::Response::build();
// Let us thank this Git repo for showing me how to do this
// https://github.com/benkay86/async-applied/blob/master/reqwest-tokio-compat/src/main.rs
let mut res_builder = res_builder
.status(rocket::http::Status::new(new_res.status().as_u16()))
.streamed_body(
new_res
.bytes_stream()
.map_err(|e| futures::io::Error::new(futures::io::ErrorKind::Other, e))
.into_async_read()
.compat(),
);
// reqwest headers -> rocket headers
for (key, value) in res_headers.iter_mut() {
res_builder = res_builder.raw_header(
String::from(key.clone().as_str()),
String::from(value.clone().to_str().unwrap()),
);
}
Outcome::Success(res_builder.finalize())
}
}