First attempt at writing reverse proxy in hyper

pull/2/head
Jef Roosens 2021-11-30 00:41:28 +01:00
parent 3e7a6a270b
commit 6735bc032b
Signed by: Jef Roosens
GPG Key ID: B580B976584B5F30
10 changed files with 400 additions and 28 deletions

142
Cargo.lock generated
View File

@ -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"

View File

@ -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" ] }

9
Rb.yaml 100644
View File

@ -0,0 +1,9 @@
debug:
address: '0.0.0.0'
port: 8000
services:
- route: '/yeet'
scheme: 'http'
authority: 'localhost:5000'
prefix: ''

69
rustfmt.toml 100644
View File

@ -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

View File

@ -0,0 +1 @@

11
src/config.rs 100644
View File

@ -0,0 +1,11 @@
use std::{net::IpAddr, path::PathBuf};
use serde::Deserialize;
#[derive(Deserialize)]
pub struct GwConfig
{
address: IpAddr,
port: u16,
services: Vec<Route>,
}

View File

@ -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<Body>) -> Result<Response<Body>, 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<Body>) -> Result<Response<Body>, 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<Body>) -> Result<Response<Body>, 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 {

5
src/proxy/mod.rs 100644
View File

@ -0,0 +1,5 @@
pub mod route;
// pub mod server;
pub use route::Route;
// pub use server::ProxyServer;

96
src/proxy/route.rs 100644
View File

@ -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<Request<Body>> for Route
{
type Response = Response<Body>;
type Error = hyper::http::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
/// This function lets hyper know when the service is ready to process requests.
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>
{
Poll::Ready(Ok(()))
}
/// Processes incoming requests by proxying them to other hosts.
fn call(&mut self, req: Request<Body>) -> 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<HttpConnector, Body> = 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)
}
}

View File

@ -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<Route>,
}
impl Service<Request<Body>> for ProxyServer
{
type Response = super::Route;
type Error = hyper::http::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
/// This function lets hyper know when the service is ready to process requests.
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>
{
Poll::Ready(Ok(()))
}
/// Processes incoming requests by proxying them to other hosts.
fn call(&mut self, req: Request<Body>) -> Self::Future {}
}