From e30915d98d8b9e44b0f27ad361c5f2af14bb02b2 Mon Sep 17 00:00:00 2001 From: Rotem Yaari Date: Sun, 4 Nov 2018 11:20:57 +0200 Subject: [PATCH] Add example of a full HTTP proxy, proxying body, header and method (#58) --- .travis.yml | 1 + Cargo.toml | 1 + http-full-proxy/Cargo.toml | 12 ++++ http-full-proxy/README.md | 10 +++ http-full-proxy/src/main.rs | 123 ++++++++++++++++++++++++++++++++++++ 5 files changed, 147 insertions(+) create mode 100644 http-full-proxy/Cargo.toml create mode 100644 http-full-proxy/README.md create mode 100644 http-full-proxy/src/main.rs diff --git a/.travis.yml b/.travis.yml index b8199db1..c1037c9d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,6 +47,7 @@ script: cd form && cargo check && cd .. cd hello-world && cargo check && cd .. cd http-proxy && cargo check && cd .. + cd http-full-proxy && cargo check && cd .. cd json && cargo check && cd .. cd juniper && cargo check && cd .. cd middleware && cargo check && cd .. diff --git a/Cargo.toml b/Cargo.toml index 7bf46b2e..d8160915 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "form", "hello-world", "http-proxy", + "http-full-proxy", "json", "juniper", "middleware", diff --git a/http-full-proxy/Cargo.toml b/http-full-proxy/Cargo.toml new file mode 100644 index 00000000..f9363344 --- /dev/null +++ b/http-full-proxy/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "http-full-proxy" +version = "0.1.0" +authors = ["Rotem Yaari"] + +[dependencies] +actix = "0.7.5" +actix-web = "0.7.13" +clap = "2.32.0" +futures = "0.1.25" +failure = "0.1.3" +url = "1.7.1" diff --git a/http-full-proxy/README.md b/http-full-proxy/README.md new file mode 100644 index 00000000..55a6a680 --- /dev/null +++ b/http-full-proxy/README.md @@ -0,0 +1,10 @@ +## HTTP Full proxy example + +This proxy forwards all types of requests, including ones with body, to another HTTP server, +returning the response to the client. + +To start: + +``` shell +cargo run +``` diff --git a/http-full-proxy/src/main.rs b/http-full-proxy/src/main.rs new file mode 100644 index 00000000..73a53b34 --- /dev/null +++ b/http-full-proxy/src/main.rs @@ -0,0 +1,123 @@ +#![deny(warnings)] +extern crate actix; +extern crate actix_web; +extern crate clap; +extern crate failure; +extern crate futures; +extern crate url; + +use actix_web::{ + client, http, server, App, AsyncResponder, Error, HttpMessage, HttpRequest, + HttpResponse, +}; +use clap::{value_t, Arg}; +use futures::Future; +use std::net::ToSocketAddrs; +use url::Url; + +struct AppState { + forward_url: Url, +} + +impl AppState { + pub fn init(forward_url: Url) -> AppState { + AppState { forward_url } + } +} + +fn forward( + req: &HttpRequest, +) -> Box> { + let mut new_url = req.state().forward_url.clone(); + new_url.set_path(req.uri().path()); + new_url.set_query(req.uri().query()); + + let mut forwarded_req = client::ClientRequest::build_from(req) + .no_default_headers() + .uri(new_url) + .streaming(req.payload()) + .unwrap(); + + if let Some(addr) = req.peer_addr() { + match forwarded_req.headers_mut().entry("x-forwarded-for") { + Ok(http::header::Entry::Vacant(entry)) => { + let addr = format!("{}", addr.ip()); + entry.insert(addr.parse().unwrap()); + } + Ok(http::header::Entry::Occupied(mut entry)) => { + let addr = format!("{}, {}", entry.get().to_str().unwrap(), addr.ip()); + entry.insert(addr.parse().unwrap()); + } + _ => unreachable!(), + } + } + + forwarded_req + .send() + .map_err(Error::from) + .and_then(move |resp| { + let mut client_resp = HttpResponse::build(resp.status()); + for (header_name, header_value) in + resp.headers().iter().filter(|(h, _)| *h != "connection") + { + client_resp.header(header_name.clone(), header_value.clone()); + } + + Ok(client_resp.streaming(resp.payload())) + }).responder() +} + +fn main() { + let matches = clap::App::new("HTTP Proxy") + .arg( + Arg::with_name("listen_addr") + .takes_value(true) + .value_name("LISTEN ADDR") + .index(1) + .required(true), + ).arg( + Arg::with_name("listen_port") + .takes_value(true) + .value_name("LISTEN PORT") + .index(2) + .required(true), + ).arg( + Arg::with_name("forward_addr") + .takes_value(true) + .value_name("FWD ADDR") + .index(3) + .required(true), + ).arg( + Arg::with_name("forward_port") + .takes_value(true) + .value_name("FWD PORT") + .index(4) + .required(true), + ).get_matches(); + + let listen_addr = matches.value_of("listen_addr").unwrap(); + let listen_port = value_t!(matches, "listen_port", u16).unwrap_or_else(|e| e.exit()); + + let forwarded_addr = matches.value_of("forward_addr").unwrap(); + let forwarded_port = + value_t!(matches, "forward_port", u16).unwrap_or_else(|e| e.exit()); + + let forward_url = Url::parse(&format!( + "http://{}", + (forwarded_addr, forwarded_port) + .to_socket_addrs() + .unwrap() + .next() + .unwrap() + )).unwrap(); + + server::new(move || { + App::with_state(AppState::init(forward_url.clone())).default_resource(|r| { + r.f(forward); + }) + }).workers(32) + .bind((listen_addr, listen_port)) + .expect("Cannot bind listening port") + .system_exit() + .run(); +}