├── .gitignore ├── src ├── proxy │ ├── mod.rs │ ├── error.rs │ ├── middleware.rs │ └── service.rs ├── middlewares │ ├── mod.rs │ ├── health.rs │ ├── logger.rs │ ├── cors.rs │ └── router.rs └── lib.rs ├── .github └── workflows │ └── rust.yml ├── Cargo.toml ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /src/proxy/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | pub mod middleware; 3 | pub mod service; 4 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Build 13 | run: cargo build --verbose 14 | - name: Run tests 15 | run: cargo test --verbose 16 | -------------------------------------------------------------------------------- /src/middlewares/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "cors")] 2 | pub mod cors; 3 | #[cfg(feature = "health")] 4 | pub mod health; 5 | pub mod logger; 6 | #[cfg(feature = "router")] 7 | pub mod router; 8 | 9 | #[cfg(feature = "cors")] 10 | pub use self::cors::Cors; 11 | #[cfg(feature = "health")] 12 | pub use self::health::Health; 13 | pub use self::logger::Logger; 14 | #[cfg(feature = "router")] 15 | pub use self::router::Router; 16 | -------------------------------------------------------------------------------- /src/middlewares/health.rs: -------------------------------------------------------------------------------- 1 | use hyper::{Body, Request, Response}; 2 | 3 | use crate::proxy::error::MiddlewareError; 4 | use crate::proxy::middleware::MiddlewareResult::{Next, RespondWith}; 5 | use crate::proxy::middleware::{Middleware, MiddlewareResult}; 6 | use crate::proxy::service::{ServiceContext, State}; 7 | 8 | pub struct Health { 9 | route: &'static str, 10 | raw_body: &'static str, 11 | } 12 | 13 | impl Health { 14 | pub fn new(route: &'static str, raw_body: &'static str) -> Self { 15 | Health { route, raw_body } 16 | } 17 | } 18 | 19 | impl Middleware for Health { 20 | fn name() -> String { 21 | String::from("Health") 22 | } 23 | 24 | fn before_request( 25 | &mut self, 26 | req: &mut Request, 27 | _context: &ServiceContext, 28 | _state: &State, 29 | ) -> Result { 30 | if req.uri().path() == self.route { 31 | let ok: Response = Response::new(Body::from(self.raw_body)); 32 | return Ok(RespondWith(ok)); 33 | } 34 | Ok(Next) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simple_proxy" 3 | edition = "2018" 4 | version = "1.3.4" 5 | authors = ["Terry Raimondo "] 6 | description = "Simple proxy with middlewares, easy to customize, easy to use." 7 | license = "Apache-2.0" 8 | homepage = "https://github.com/terry90/rs-simple-proxy" 9 | repository = "https://github.com/terry90/rs-simple-proxy" 10 | 11 | [package.metadata.docs.rs] 12 | features = ["docs"] 13 | 14 | [features] 15 | router = ["regex", "serde_regex"] 16 | health = [] 17 | cors = [] 18 | docs = ["router", "health", "cors"] 19 | 20 | [dependencies] 21 | futures = "0.3.5" 22 | log = "0.4.8" 23 | chrono = { version = "0.4.11", features = ["serde"] } 24 | regex = { version = "1.3.9", optional = true } 25 | serde_regex = { version = "1.1.0", optional = true } 26 | serde_json = "1.0.55" 27 | serde_derive = "1.0.112" 28 | serde = "1.0.112" 29 | rand = { version = "0.8.3", features = ["small_rng"] } 30 | hyper = { version = "0.14.5", features = ["client", "tcp", "http1", "server"] } 31 | http = "0.2.1" 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple proxy 2 | 3 | ## Usage 4 | 5 | ```rust 6 | use simple_proxy::middlewares::{router::*, Logger}; 7 | use simple_proxy::{Environment, SimpleProxy}; 8 | 9 | use structopt::StructOpt; 10 | 11 | #[derive(StructOpt, Debug)] 12 | struct Cli { 13 | port: u16, 14 | } 15 | 16 | #[derive(Debug, Clone)] 17 | pub struct Config(); 18 | 19 | impl RouterConfig for Config { 20 | fn get_router_filename(&self) -> &'static str { 21 | "routes.json" 22 | } 23 | } 24 | 25 | #[tokio::main] 26 | async fn main() { 27 | let args = Cli::from_args(); 28 | 29 | let mut proxy = SimpleProxy::new(args.port, Environment::Development); 30 | let logger = Logger::new(); 31 | let router = Router::new(&Config()); 32 | 33 | // Order matters 34 | proxy.add_middleware(Box::new(router)); 35 | proxy.add_middleware(Box::new(logger)); 36 | 37 | // Start proxy 38 | let _ = proxy.run().await; 39 | } 40 | ``` 41 | 42 | ### Custom middleware 43 | 44 | You can create your custom middleware by creating a struct implementing Middleware, consisting of 4 callbacks: 45 | 46 | - `before_request` will be run every time 47 | - `request_failure` will be run when the request fails 48 | - `request_success` will be run when the request succeeds, you can then handle the response according to the status code or the body 49 | - `after_request` will be run every time 50 | 51 | #### For more info, see a [default middleware](src/middlewares/logger.rs) 52 | -------------------------------------------------------------------------------- /src/proxy/error.rs: -------------------------------------------------------------------------------- 1 | use hyper::{Body, Response, StatusCode}; 2 | use std::error::Error; 3 | 4 | #[derive(Debug)] 5 | pub struct MiddlewareError { 6 | pub description: String, 7 | pub body: String, 8 | pub status: StatusCode, 9 | } 10 | 11 | impl From for Response { 12 | fn from(err: MiddlewareError) -> Response { 13 | err.to_json_response() 14 | } 15 | } 16 | 17 | impl MiddlewareError { 18 | pub fn new(description: String, body: Option, status: StatusCode) -> MiddlewareError { 19 | let body = match body { 20 | Some(body) => body, 21 | None => { 22 | // Uncatched error 23 | let err = format!("Internal proxy server error: {}", &description); 24 | error!("{}", &err); 25 | err 26 | } 27 | }; 28 | 29 | debug!("Middleware error: {}", &description); 30 | 31 | MiddlewareError { 32 | description, 33 | status, 34 | body, 35 | } 36 | } 37 | 38 | pub fn to_json_response(&self) -> Response { 39 | Response::builder() 40 | .header("Content-Type", "application/json") 41 | .status(self.status) 42 | .body(Body::from(format!("{{\"error\":\"{}\"}}", self.body))) 43 | .unwrap() 44 | } 45 | } 46 | 47 | impl From for MiddlewareError 48 | where 49 | E: Error, 50 | { 51 | fn from(err: E) -> MiddlewareError { 52 | MiddlewareError::new(err.to_string(), None, StatusCode::INTERNAL_SERVER_ERROR) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/middlewares/logger.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use hyper::{Body, Request, Response}; 3 | use serde_json; 4 | 5 | use crate::proxy::error::MiddlewareError; 6 | use crate::proxy::middleware::MiddlewareResult::Next; 7 | use crate::proxy::middleware::{Middleware, MiddlewareResult}; 8 | use crate::proxy::service::{ServiceContext, State}; 9 | 10 | #[derive(Clone, Default)] 11 | pub struct Logger; 12 | 13 | /// # Panics 14 | /// May panic if the request state has not been initialized in `before_request`. 15 | /// e.g If a middleware responded early before the logger in `before_request`. 16 | impl Middleware for Logger { 17 | fn name() -> String { 18 | String::from("Logger") 19 | } 20 | 21 | fn before_request( 22 | &mut self, 23 | req: &mut Request, 24 | context: &ServiceContext, 25 | state: &State, 26 | ) -> Result { 27 | info!( 28 | "[{}] Starting a {} request to {}", 29 | &context.req_id.to_string()[..6], 30 | req.method(), 31 | req.uri() 32 | ); 33 | let now = serde_json::to_string(&Utc::now()).expect("[Logger] Cannot serialize DateTime"); 34 | self.set_state(context.req_id, state, now)?; 35 | Ok(Next) 36 | } 37 | 38 | fn after_request( 39 | &mut self, 40 | _res: Option<&mut Response>, 41 | context: &ServiceContext, 42 | state: &State, 43 | ) -> Result { 44 | let start_time = self.get_state(context.req_id, state)?; 45 | match start_time { 46 | Some(time) => { 47 | let start_time: DateTime = serde_json::from_str(&time)?; 48 | 49 | info!( 50 | "[{}] Request took {}ms", 51 | &context.req_id.to_string()[..6], 52 | (Utc::now() - start_time).num_milliseconds() 53 | ); 54 | } 55 | None => error!("[Logger] start time not found in state"), 56 | } 57 | Ok(Next) 58 | } 59 | } 60 | 61 | impl Logger { 62 | pub fn new() -> Self { 63 | Logger {} 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/middlewares/cors.rs: -------------------------------------------------------------------------------- 1 | use hyper::header::HeaderValue; 2 | use hyper::{Body, Method, Request, Response}; 3 | 4 | use crate::proxy::error::MiddlewareError; 5 | use crate::proxy::middleware::MiddlewareResult::Next; 6 | use crate::proxy::middleware::MiddlewareResult::RespondWith; 7 | use crate::proxy::middleware::{Middleware, MiddlewareResult}; 8 | use crate::proxy::service::{ServiceContext, State}; 9 | 10 | pub struct Cors { 11 | allow_origin: &'static str, 12 | allow_methods: &'static str, 13 | allow_headers: &'static str, 14 | } 15 | 16 | impl Cors { 17 | pub fn new( 18 | allow_origin: &'static str, 19 | allow_methods: &'static str, 20 | allow_headers: &'static str, 21 | ) -> Self { 22 | Cors { 23 | allow_origin, 24 | allow_methods, 25 | allow_headers, 26 | } 27 | } 28 | 29 | fn set_cors_headers(&self, response: &mut Response) { 30 | response.headers_mut().insert( 31 | "Access-Control-Allow-Origin", 32 | HeaderValue::from_static(self.allow_origin), 33 | ); 34 | response.headers_mut().insert( 35 | "Access-Control-Allow-Methods", 36 | HeaderValue::from_static(self.allow_methods), 37 | ); 38 | response.headers_mut().insert( 39 | "Access-Control-Allow-Headers", 40 | HeaderValue::from_static(self.allow_headers), 41 | ); 42 | } 43 | } 44 | 45 | impl Middleware for Cors { 46 | fn name() -> String { 47 | String::from("Cors") 48 | } 49 | 50 | fn before_request( 51 | &mut self, 52 | req: &mut Request, 53 | _context: &ServiceContext, 54 | _state: &State, 55 | ) -> Result { 56 | if req.method() == Method::OPTIONS { 57 | let mut response: Response = Response::new(Body::from("")); 58 | self.set_cors_headers(&mut response); 59 | 60 | return Ok(RespondWith(response)); 61 | } 62 | Ok(Next) 63 | } 64 | 65 | fn after_request( 66 | &mut self, 67 | response: Option<&mut Response>, 68 | _context: &ServiceContext, 69 | _state: &State, 70 | ) -> Result { 71 | if let Some(res) = response { 72 | self.set_cors_headers(res); 73 | } 74 | Ok(Next) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/proxy/middleware.rs: -------------------------------------------------------------------------------- 1 | use crate::proxy::error::MiddlewareError; 2 | use crate::proxy::service::{ServiceContext, State}; 3 | use hyper::{Body, Error, Request, Response}; 4 | 5 | pub enum MiddlewareResult { 6 | RespondWith(Response), 7 | Next, 8 | } 9 | 10 | use self::MiddlewareResult::Next; 11 | 12 | pub trait Middleware { 13 | fn name() -> String 14 | where 15 | Self: Sized; 16 | 17 | fn get_name(&self) -> String 18 | where 19 | Self: Sized, 20 | { 21 | Self::name() 22 | } 23 | 24 | fn set_state(&self, req_id: u64, state: &State, data: String) -> Result<(), MiddlewareError> 25 | where 26 | Self: Sized, 27 | { 28 | let mut state = state.lock()?; 29 | state.insert((self.get_name(), req_id), data); 30 | Ok(()) 31 | } 32 | 33 | fn state(req_id: u64, state: &State) -> Result, MiddlewareError> 34 | where 35 | Self: Sized, 36 | { 37 | let state = state.lock()?; 38 | debug!("State length: {}", state.len()); 39 | let state = match state.get(&(Self::name(), req_id)) { 40 | None => None, 41 | Some(state) => Some(state.to_string()), 42 | }; 43 | 44 | debug!( 45 | "[{}] State for {}: {:?}", 46 | Self::name(), 47 | &req_id.to_string()[..6], 48 | state 49 | ); 50 | 51 | Ok(state) 52 | } 53 | 54 | fn get_state(&self, req_id: u64, state: &State) -> Result, MiddlewareError> 55 | where 56 | Self: Sized, 57 | { 58 | Self::state(req_id, state) 59 | } 60 | 61 | fn before_request( 62 | &mut self, 63 | _req: &mut Request, 64 | _ctx: &ServiceContext, 65 | _state: &State, 66 | ) -> Result { 67 | Ok(Next) 68 | } 69 | 70 | fn after_request( 71 | &mut self, 72 | _res: Option<&mut Response>, 73 | _ctx: &ServiceContext, 74 | _state: &State, 75 | ) -> Result { 76 | Ok(Next) 77 | } 78 | 79 | fn request_failure( 80 | &mut self, 81 | _err: &Error, 82 | _ctx: &ServiceContext, 83 | _state: &State, 84 | ) -> Result { 85 | Ok(Next) 86 | } 87 | 88 | fn request_success( 89 | &mut self, 90 | _res: &mut Response, 91 | _ctx: &ServiceContext, 92 | _state: &State, 93 | ) -> Result { 94 | Ok(Next) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | #[cfg(feature = "router")] 4 | #[macro_use] 5 | extern crate serde_derive; 6 | 7 | pub mod middlewares; 8 | pub mod proxy; 9 | 10 | use hyper::server::conn::AddrStream; 11 | use hyper::service::make_service_fn; 12 | use hyper::Server; 13 | use std::fmt; 14 | use std::{ 15 | convert::Infallible, 16 | sync::{Arc, Mutex}, 17 | }; 18 | 19 | use crate::proxy::middleware::Middleware; 20 | use crate::proxy::service::ProxyService; 21 | 22 | type Middlewares = Arc>>>; 23 | 24 | #[derive(Debug, Clone, Copy)] 25 | pub enum Environment { 26 | Production, 27 | Staging, 28 | Development, 29 | } 30 | 31 | impl fmt::Display for Environment { 32 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 33 | match self { 34 | Environment::Production => write!(f, "production"), 35 | Environment::Staging => write!(f, "staging"), 36 | Environment::Development => write!(f, "development"), 37 | } 38 | } 39 | } 40 | 41 | impl std::str::FromStr for Environment { 42 | type Err = String; 43 | 44 | fn from_str(s: &str) -> Result { 45 | match s { 46 | "production" => Ok(Environment::Production), 47 | "staging" => Ok(Environment::Staging), 48 | "development" => Ok(Environment::Development), 49 | _ => Err(String::from( 50 | "valid values: production, staging, development", 51 | )), 52 | } 53 | } 54 | } 55 | 56 | pub struct SimpleProxy { 57 | port: u16, 58 | environment: Environment, 59 | middlewares: Middlewares, 60 | } 61 | 62 | impl SimpleProxy { 63 | pub fn new(port: u16, environment: Environment) -> Self { 64 | SimpleProxy { 65 | port, 66 | environment, 67 | middlewares: Arc::new(Mutex::new(vec![])), 68 | } 69 | } 70 | 71 | pub async fn run(&self) -> Result<(), Box> { 72 | let addr = ([0, 0, 0, 0], self.port).into(); 73 | 74 | info!("Running proxy in {} mode on: {}", self.environment, &addr); 75 | 76 | let middlewares = Arc::clone(&self.middlewares); 77 | let make_svc = make_service_fn(move |socket: &AddrStream| { 78 | let remote_addr = socket.remote_addr(); 79 | let middlewares = middlewares.clone(); 80 | debug!("Handling connection for IP: {}", &remote_addr); 81 | 82 | async move { Ok::<_, Infallible>(ProxyService::new(middlewares, remote_addr)) } 83 | }); 84 | 85 | let server = Server::bind(&addr).serve(make_svc); 86 | 87 | if let Err(e) = server.await { 88 | eprintln!("server error: {}", e); 89 | } 90 | Ok(()) 91 | } 92 | 93 | pub fn add_middleware(&mut self, middleware: Box) { 94 | self.middlewares.lock().unwrap().push(middleware) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/middlewares/router.rs: -------------------------------------------------------------------------------- 1 | use http::uri::{Parts, Uri}; 2 | use hyper::header::HeaderValue; 3 | use hyper::{Body, Request, StatusCode}; 4 | use regex::Regex; 5 | 6 | use crate::proxy::error::MiddlewareError; 7 | use crate::proxy::middleware::MiddlewareResult::Next; 8 | use crate::proxy::middleware::{Middleware, MiddlewareResult}; 9 | use crate::proxy::service::{ServiceContext, State}; 10 | 11 | use serde_json; 12 | 13 | #[derive(Clone)] 14 | pub struct Router { 15 | routes: RouterRules, 16 | name: String, 17 | } 18 | 19 | #[derive(Debug, Clone, Deserialize)] 20 | pub struct RouteRegex { 21 | #[serde(with = "serde_regex")] 22 | pub host: Regex, 23 | #[serde(with = "serde_regex")] 24 | pub path: Regex, 25 | } 26 | 27 | #[derive(Debug, Clone, Deserialize)] 28 | pub struct Route { 29 | pub from: RouteRegex, 30 | pub to: RouteRegex, 31 | pub public: bool, 32 | } 33 | 34 | #[derive(Debug, Clone, Deserialize)] 35 | pub struct RouterRulesWrapper { 36 | pub rules: RouterRules, 37 | } 38 | 39 | pub type RouterRules = Vec; 40 | 41 | #[derive(Serialize, Deserialize, Debug)] 42 | pub struct MatchedRoute { 43 | pub uri: String, 44 | pub public: bool, 45 | } 46 | 47 | pub trait RouterConfig { 48 | fn get_router_filename(&self) -> &str; 49 | } 50 | 51 | fn get_host_and_path(req: &mut Request) -> Result<(String, String), MiddlewareError> { 52 | let uri = req.uri(); 53 | let path = uri 54 | .path_and_query() 55 | .map(ToString::to_string) 56 | .unwrap_or_else(|| String::from("")); 57 | 58 | match uri.host() { 59 | Some(host) => Ok((String::from(host), path)), 60 | None => Ok(( 61 | String::from(req.headers().get("host").unwrap().to_str()?), 62 | path, 63 | )), 64 | } 65 | } 66 | 67 | fn inject_new_uri( 68 | req: &mut Request, 69 | old_host: &str, 70 | host: &str, 71 | path: &str, 72 | ) -> Result<(), MiddlewareError> { 73 | { 74 | let headers = req.headers_mut(); 75 | 76 | headers.insert("X-Forwarded-Host", HeaderValue::from_str(old_host).unwrap()); 77 | headers.insert("host", HeaderValue::from_str(host).unwrap()); 78 | } 79 | let mut parts = Parts::default(); 80 | parts.scheme = Some("http".parse()?); 81 | parts.authority = Some(host.parse()?); 82 | parts.path_and_query = Some(path.parse()?); 83 | 84 | debug!("Found a route to {:?}", parts); 85 | 86 | *req.uri_mut() = Uri::from_parts(parts)?; 87 | 88 | Ok(()) 89 | } 90 | 91 | impl Middleware for Router { 92 | fn name() -> String { 93 | String::from("Router") 94 | } 95 | 96 | fn before_request( 97 | &mut self, 98 | req: &mut Request, 99 | context: &ServiceContext, 100 | state: &State, 101 | ) -> Result { 102 | let routes = &self.routes; 103 | 104 | let (host, path) = get_host_and_path(req)?; 105 | debug!("Routing => Host: {} Path: {}", host, path); 106 | 107 | for route in routes { 108 | let (re_host, re_path) = (&route.from.host, &route.from.path); 109 | let to = &route.to; 110 | let public = route.public; 111 | 112 | debug!("Trying to convert from {} / {:?}", &re_host, &re_path); 113 | 114 | if re_host.is_match(&host) { 115 | let new_host = re_host.replace(&host, to.host.as_str()); 116 | 117 | let new_path = if re_path.is_match(&path) { 118 | re_path.replace(&path, to.path.as_str()) 119 | } else { 120 | continue; 121 | }; 122 | 123 | debug!("Proxying to {}", &new_host); 124 | inject_new_uri(req, &host, &new_host, &new_path)?; 125 | self.set_state( 126 | context.req_id, 127 | state, 128 | serde_json::to_string(&MatchedRoute { 129 | uri: req.uri().to_string(), 130 | public, 131 | })?, 132 | )?; 133 | return Ok(Next); 134 | } 135 | } 136 | 137 | Err(MiddlewareError::new( 138 | String::from("No route matched"), 139 | Some(String::from("Not found")), 140 | StatusCode::NOT_FOUND, 141 | )) 142 | } 143 | } 144 | 145 | fn read_routes(config: &T) -> RouterRules { 146 | use std::fs::File; 147 | use std::io::prelude::Read; 148 | 149 | let mut f = File::open(config.get_router_filename()).expect("Router config not found !"); 150 | 151 | let mut data = String::new(); 152 | f.read_to_string(&mut data) 153 | .expect("Cannot read Router config !"); 154 | 155 | let rules: RouterRulesWrapper = 156 | serde_json::from_str(&data).expect("Cannot parse Router config file !"); 157 | 158 | rules.rules 159 | } 160 | 161 | impl Router { 162 | pub fn new(config: &T) -> Self { 163 | Router { 164 | routes: read_routes(config), 165 | name: String::from("Router"), 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/proxy/service.rs: -------------------------------------------------------------------------------- 1 | use futures::future; 2 | use futures::future::TryFutureExt; 3 | use hyper::client::connect::HttpConnector; 4 | use hyper::service::Service; 5 | use hyper::{Body, Client, Request, Response}; 6 | use std::future::Future; 7 | 8 | use std::collections::HashMap; 9 | use std::net::SocketAddr; 10 | use std::{ 11 | pin::Pin, 12 | sync::{Arc, Mutex}, 13 | task::{Context, Poll}, 14 | }; 15 | 16 | use rand::prelude::*; 17 | use rand::rngs::SmallRng; 18 | 19 | use crate::proxy::middleware::MiddlewareResult::*; 20 | use crate::Middlewares; 21 | 22 | // type BoxFut = Box, hyper::Error>> + Send>; 23 | pub type State = Arc>>; 24 | 25 | pub struct ProxyService { 26 | client: Client, 27 | middlewares: Middlewares, 28 | state: State, 29 | remote_addr: SocketAddr, 30 | rng: SmallRng, 31 | } 32 | 33 | #[derive(Clone, Copy)] 34 | pub struct ServiceContext { 35 | pub remote_addr: SocketAddr, 36 | pub req_id: u64, 37 | } 38 | 39 | impl Service> for ProxyService { 40 | type Response = Response; 41 | type Error = hyper::Error; 42 | type Future = Pin> + Send>>; 43 | 44 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 45 | match self.client.poll_ready(cx) { 46 | Poll::Ready(Ok(())) => Poll::Ready(Ok(())), 47 | Poll::Ready(Err(e)) => Poll::Ready(Err(e)), 48 | Poll::Pending => Poll::Pending, 49 | } 50 | } 51 | 52 | fn call(&mut self, req: Request) -> Self::Future { 53 | self.clear_state(); 54 | let (parts, body) = req.into_parts(); 55 | let mut req = Request::from_parts(parts, body); 56 | 57 | // Create references for future callbacks 58 | // references are moved in each chained future (map,then..) 59 | let mws_failure = Arc::clone(&self.middlewares); 60 | let mws_success = Arc::clone(&self.middlewares); 61 | let mws_after_success = Arc::clone(&self.middlewares); 62 | let mws_after_failure = Arc::clone(&self.middlewares); 63 | let state_failure = Arc::clone(&self.state); 64 | let state_success = Arc::clone(&self.state); 65 | let state_after_success = Arc::clone(&self.state); 66 | let state_after_failure = Arc::clone(&self.state); 67 | 68 | let req_id = self.rng.next_u64(); 69 | 70 | let context = ServiceContext { 71 | req_id, 72 | remote_addr: self.remote_addr, 73 | }; 74 | 75 | let mut before_res: Option> = None; 76 | for mw in self.middlewares.lock().unwrap().iter_mut() { 77 | // Run all middlewares->before_request 78 | if let Some(res) = match mw.before_request(&mut req, &context, &self.state) { 79 | Err(err) => Some(Response::from(err)), 80 | Ok(RespondWith(response)) => Some(response), 81 | Ok(Next) => None, 82 | } { 83 | // Stop when an early response is wanted 84 | before_res = Some(res); 85 | break; 86 | } 87 | } 88 | 89 | if let Some(res) = before_res { 90 | return Box::pin(future::ok(self.early_response(&context, res, &self.state))); 91 | } 92 | 93 | let res = self 94 | .client 95 | .request(req) 96 | .map_err(move |err| { 97 | for mw in mws_failure.lock().unwrap().iter_mut() { 98 | // TODO: think about graceful handling 99 | if let Err(err) = mw.request_failure(&err, &context, &state_failure) { 100 | error!("Request_failure errored: {:?}", &err); 101 | } 102 | } 103 | err 104 | }) 105 | .map_ok(move |mut res| { 106 | for mw in mws_success.lock().unwrap().iter_mut() { 107 | match mw.request_success(&mut res, &context, &state_success) { 108 | Err(err) => res = Response::from(err), 109 | Ok(RespondWith(response)) => res = response, 110 | Ok(Next) => (), 111 | } 112 | } 113 | res 114 | }) 115 | .map_ok_or_else( 116 | move |err| { 117 | let mut res = Err(err); 118 | for mw in mws_after_success.lock().unwrap().iter_mut() { 119 | match mw.after_request(None, &context, &state_after_success) { 120 | Err(err) => res = Ok(Response::from(err)), 121 | Ok(RespondWith(response)) => res = Ok(response), 122 | Ok(Next) => (), 123 | } 124 | } 125 | res 126 | }, 127 | move |mut res| { 128 | for mw in mws_after_failure.lock().unwrap().iter_mut() { 129 | match mw.after_request(Some(&mut res), &context, &state_after_failure) { 130 | Err(err) => res = Response::from(err), 131 | Ok(RespondWith(response)) => res = response, 132 | Ok(Next) => (), 133 | } 134 | } 135 | Ok(res) 136 | }, 137 | ); 138 | 139 | Box::pin(res) 140 | } 141 | } 142 | 143 | impl ProxyService { 144 | fn early_response( 145 | &self, 146 | context: &ServiceContext, 147 | mut res: Response, 148 | state: &State, 149 | ) -> Response { 150 | for mw in self.middlewares.lock().unwrap().iter_mut() { 151 | match mw.after_request(Some(&mut res), context, state) { 152 | Err(err) => res = Response::from(err), 153 | Ok(RespondWith(response)) => res = response, 154 | Ok(Next) => (), 155 | } 156 | } 157 | debug!("Early response is {:?}", &res); 158 | res 159 | } 160 | 161 | // Needed to avoid a single connection creating too much data in state 162 | // Since we need to identify each request in state (HashMap tuple identifier), it grows 163 | // for each request from the same connection 164 | fn clear_state(&self) { 165 | if let Ok(mut state) = self.state.lock() { 166 | state.clear(); 167 | } else { 168 | error!("[FATAL] Cannot lock state in clean_stale_state"); 169 | } 170 | } 171 | 172 | pub fn new(middlewares: Middlewares, remote_addr: SocketAddr) -> Self { 173 | ProxyService { 174 | client: Client::new(), 175 | state: Arc::new(Mutex::new(HashMap::new())), 176 | rng: SmallRng::from_entropy(), 177 | remote_addr, 178 | middlewares, 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [2018] [Terry Raimondo] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------