diff --git a/Cargo.lock b/Cargo.lock index 0642250..9653684 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "atomic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" +dependencies = [ + "autocfg", +] + [[package]] name = "autocfg" version = "1.0.1" @@ -26,6 +35,26 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] +name = "figment" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790b4292c72618abbab50f787a477014fe15634f96291de45672ce46afe122df" +dependencies = [ + "atomic", + "pear", + "serde", + "serde_yaml", + "uncased", + "version_check", +] + [[package]] name = "fnv" version = "1.0.7" @@ -173,6 +202,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inlinable_string" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3094308123a0e9fd59659ce45e22de9f53fc1d2ac6e1feb9fef988e4f76cad77" + [[package]] name = "instant" version = "0.1.12" @@ -200,6 +235,12 @@ version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + [[package]] name = "lock_api" version = "0.4.5" @@ -296,6 +337,29 @@ dependencies = [ "winapi", ] +[[package]] +name = "pear" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.7" @@ -317,6 +381,19 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", + "yansi", +] + [[package]] name = "quote" version = "1.0.10" @@ -330,8 +407,9 @@ dependencies = [ name = "rb-gw_v2" version = "0.1.0" dependencies = [ - "http", + "figment", "hyper", + "serde", "tokio", ] @@ -350,6 +428,38 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c608a35705a5d3cdc9fbe403147647ff34b921f8e833e49306df898f9b20af" +dependencies = [ + "dtoa", + "indexmap", + "serde", + "yaml-rust", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -469,12 +579,27 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "uncased" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + [[package]] name = "want" version = "0.3.0" @@ -506,3 +631,18 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "yansi" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" diff --git a/Cargo.toml b/Cargo.toml index 44fec13..acb3727 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,5 @@ edition = "2018" [dependencies] hyper = { version = "0.14.15", features = [ "full" ] } tokio = { version = "*", features = [ "full" ] } -http = "*" +figment = { version = "0.10.6", features = [ "yaml", "env" ] } +serde = { version = "*", features = [ "derive" ] } diff --git a/Rb.yaml b/Rb.yaml new file mode 100644 index 0000000..e9ea981 --- /dev/null +++ b/Rb.yaml @@ -0,0 +1,9 @@ +debug: + address: '0.0.0.0' + port: 8000 + + services: + - route: '/yeet' + scheme: 'http' + authority: 'localhost:5000' + prefix: '' diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..1d443c7 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,69 @@ +required_version = "1.4.38" +binop_separator = "Front" +blank_lines_lower_bound = 0 +blank_lines_upper_bound = 1 +# Trying something new +brace_style = "AlwaysNextLine" +color = "Auto" +combine_control_expr = false +comment_width = 80 +condense_wildcard_suffixes = false +control_brace_style = "AlwaysSameLine" +disable_all_formatting = false +edition = "2018" +emit_mode = "Files" +empty_item_single_line = true +enum_discrim_align_threshold = 0 +error_on_line_overflow = false +error_on_unformatted = false +fn_args_layout = "Tall" +fn_single_line = false +force_explicit_abi = true +force_multiline_blocks = false +format_code_in_doc_comments = false +format_macro_bodies = true +format_macro_matchers = false +format_strings = false +group_imports = "StdExternalCrate" +hard_tabs = false +hide_parse_errors = false +ignore = [] +imports_granularity = "Crate" +imports_indent = "Block" +imports_layout = "Mixed" +indent_style = "Block" +inline_attribute_width = 0 +license_template_path = "" +make_backup = false +match_arm_blocks = true +match_arm_leading_pipes = "Never" +match_block_trailing_comma = true +max_width = 100 +merge_derives = true +newline_style = "Auto" +normalize_comments = false +normalize_doc_attributes = false +overflow_delimited_expr = false +remove_nested_parens = true +reorder_impl_items = false +reorder_imports = true +reorder_modules = true +report_fixme = "Always" +report_todo = "Always" +skip_children = false +space_after_colon = true +space_before_colon = false +spaces_around_ranges = false +struct_field_align_threshold = 0 +struct_lit_single_line = true +tab_spaces = 4 +trailing_comma = "Vertical" +trailing_semicolon = true +type_punctuation_density = "Wide" +unstable_features = false +use_field_init_shorthand = false +use_small_heuristics = "Default" +use_try_shorthand = false +version = "One" +where_single_line = false +wrap_comments = false diff --git a/src/client.rs b/src/client.rs index e69de29..8b13789 100644 --- a/src/client.rs +++ b/src/client.rs @@ -0,0 +1 @@ + diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..f96c7db --- /dev/null +++ b/src/config.rs @@ -0,0 +1,11 @@ +use std::{net::IpAddr, path::PathBuf}; + +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct GwConfig +{ + address: IpAddr, + port: u16, + services: Vec, +} diff --git a/src/main.rs b/src/main.rs index 7170403..cda5489 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,29 +1,23 @@ -use hyper::client::Client; -use hyper::client::HttpConnector; -use hyper::http::uri::Authority; -use hyper::http::uri::Parts; -use hyper::http::uri::Scheme; -use hyper::service::{make_service_fn, service_fn}; -use hyper::Uri; -use hyper::{Body, Request, Response, Server}; -use std::convert::Infallible; use std::net::SocketAddr; -pub mod client; +use figment::{ + providers::{Env, Format, Yaml}, + Figment, +}; +use hyper::{ + client::{Client, HttpConnector}, + http::uri::{Authority, Parts, Scheme}, + service::{make_service_fn, service_fn}, + Body, Request, Response, Server, Uri, +}; +use proxy::Route; -async fn handle(mut req: Request) -> Result, hyper::Error> { - // We modify the request by changing its URI. - // let uri = req.uri(); - // let scheme = uri.scheme().unwrap_or(&Scheme::HTTP); - // let authority = "localhost:5000"; - // let path_and_query = - // println!("{:?}", uri); - // let new_uri = Uri::builder() - // .scheme(uri.scheme().clone().unwrap_or(&Scheme::HTTP)) - // .authority("http://localhost:5000") - // .path_and_query(uri.path_and_query().unwrap().clone()) - // .build() - // .unwrap(); +pub mod client; +mod config; +mod proxy; + +async fn handle(mut req: Request) -> Result, hyper::Error> +{ println!("{:?}", req); let req_parts = req.uri().clone().into_parts(); let mut parts = Parts::default(); @@ -40,15 +34,23 @@ async fn handle(mut req: Request) -> Result, hyper::Error> } #[tokio::main] -async fn main() { +async fn main() +{ + // let figment = Figment::new() + // .merge(Yaml::file("Rb.yaml").nested()) + // .merge(Env::prefixed("RB_").global()); + // let config: config::GwConfig = figment.extract().unwrap(); + // Construct our SocketAddr to listen on... let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); // And a MakeService to handle each connection... - let make_service = make_service_fn(|_conn| async { Ok::<_, hyper::Error>(service_fn(handle)) }); + // let make_service = make_service_fn(|_conn| async { Ok::<_, hyper::Error>(service_fn(handle)) }); // Then bind and serve... - let server = Server::bind(&addr).serve(make_service); + let route = Route::new("/".into(), String::from("localhost:8000"), "/".into()); + let make_svc = make_service_fn(|_| async { Ok::<_, hyper::Error>(route) }); + let server = Server::bind(&addr).serve(make_svc); // And run forever... if let Err(e) = server.await { diff --git a/src/proxy/mod.rs b/src/proxy/mod.rs new file mode 100644 index 0000000..8e057c4 --- /dev/null +++ b/src/proxy/mod.rs @@ -0,0 +1,5 @@ +pub mod route; +// pub mod server; + +pub use route::Route; +// pub use server::ProxyServer; diff --git a/src/proxy/route.rs b/src/proxy/route.rs new file mode 100644 index 0000000..4c26dfa --- /dev/null +++ b/src/proxy/route.rs @@ -0,0 +1,96 @@ +use core::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; +use std::path::PathBuf; + +use hyper::{ + client::{Client, HttpConnector}, + http::{ + uri::{Authority, Parts, Scheme}, + Request, Response, + }, + service::Service, + Body, Uri, +}; +use serde::Deserialize; + +/// Represents a route that a ProxyServer can send requests to. This struct also implements the +/// `hyper::service::Service` trait, meaning it can be used as a service for a hyper server to +/// consume. This is used in combination with `crate::proxy::ProxyServer` to create a modular proxy +/// server. +/// +/// # Fields +/// +/// * `route` - Which prefix of route should be matched. This route prefix will then be replaced by +/// the provided prefix when routing. +/// * `host` - The host that request should be proxied to. This variable should also include the +/// port number if necessary. +/// * `prefix` - The prefix with which to replace the routed prefix. In practice, this means that +/// the proxy can use a completely different prefix for routes, e.g. `/api/posts` on the proxy site +/// & `/v1/api/posts` on the proxied server site. +/// * `timeout` - How long the proxy should wait before aborting the request. +#[derive(Deserialize)] +pub struct Route +{ + route: PathBuf, + host: String, + prefix: PathBuf, +} + +impl Route +{ + pub fn new(route: PathBuf, host: String, prefix: PathBuf) -> Route + { + Route { + route, + host, + prefix, + } + } +} + +impl Service> for Route +{ + type Response = Response; + type Error = hyper::http::Error; + type Future = Pin>>>; + + /// This function lets hyper know when the service is ready to process requests. + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> + { + Poll::Ready(Ok(())) + } + + /// Processes incoming requests by proxying them to other hosts. + fn call(&mut self, req: Request) -> Self::Future + { + let fut = async { + // Convert the request's uri to match the redirected one + let req_parts = req.uri().clone().into_parts(); + let mut parts = Parts::default(); + parts.scheme = req_parts.scheme.or_else(|| Some(Scheme::HTTP)); + parts.authority = Some(Authority::from_static("localhost:5000")); + parts.path_and_query = req_parts.path_and_query; + + let new_uri = Uri::from_parts(parts).unwrap(); + *req.uri_mut() = new_uri; + + // Create a new client that can send the requests + let client: Client = Client::builder().build_http(); + + // match client.request + // NOTE: when should we return an Err here? + match client.request(req).await { + Ok(res) => Ok(res), + Err(_) => Ok(hyper::http::response::Builder::new() + .status(500) + .body(Body::empty()) + .unwrap()), + } + }; + + Box::pin(fut) + } +} diff --git a/src/proxy/server.rs b/src/proxy/server.rs new file mode 100644 index 0000000..38d2fe4 --- /dev/null +++ b/src/proxy/server.rs @@ -0,0 +1,38 @@ +use core::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; +use std::path::PathBuf; + +use hyper::{ + client::{Client, HttpConnector}, + http::{Request, Response}, + service::Service, + Body, +}; + +use super::route::Route; + +/// A ProxyServer is an implementation of `hyper::service::Service` that proxies requests to a +/// given list of routes. +pub struct ProxyServer +{ + routes: Vec, +} + +impl Service> for ProxyServer +{ + type Response = super::Route; + type Error = hyper::http::Error; + type Future = Pin>>>; + + /// This function lets hyper know when the service is ready to process requests. + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> + { + Poll::Ready(Ok(())) + } + + /// Processes incoming requests by proxying them to other hosts. + fn call(&mut self, req: Request) -> Self::Future {} +}