First attempt at writing reverse proxy in hyper

This commit is contained in:
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

View file

@ -0,0 +1 @@

11
src/config.rs Normal file
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 Normal file
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 Normal file
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)
}
}

38
src/proxy/server.rs Normal file
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 {}
}