Skip to content
This repository has been archived by the owner on Aug 3, 2023. It is now read-only.

Commit

Permalink
Refactor proxy and add IP argument
Browse files Browse the repository at this point in the history
  • Loading branch information
EverlastingBugstopper committed Nov 15, 2019
1 parent 0892328 commit 5b721c4
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 93 deletions.
237 changes: 146 additions & 91 deletions src/commands/preview/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ extern crate hyper_tls;
extern crate pretty_env_logger;
extern crate url;

use std::net::{SocketAddr, ToSocketAddrs};

use chrono::prelude::*;

use hyper::header::{HeaderName, HeaderValue};
Expand All @@ -13,111 +15,164 @@ use hyper::{Client, Server};

use hyper_tls::HttpsConnector;

use failure::format_err;

use uuid::Uuid;

use url::Url;

use crate::settings::global_user::GlobalUser;
use crate::settings::target::Target;

use crate::commands;
use crate::commands::preview::upload;

const PREVIEW_HOST: &str = "rawhttp.cloudflareworkers.com";

struct Proxy {
host: String,
listening_address: SocketAddr,
is_https: bool,
}

impl Proxy {
pub fn new(
host: Option<&str>,
ip: Option<&str>,
port: Option<&str>,
) -> Result<Self, failure::Error> {
let port: &str = match port {
Some(port) => port,
None => "8000",
};

let try_address = match ip {
Some(ip) => format!("{}:{}", ip, port),
None => format!("localhost:{}", port),
};

let mut address_iter = try_address.to_socket_addrs()?;

let listening_address = match address_iter.next() {
Some(ip) => Ok(ip),
None => Err(format_err!("Could not parse address {}", try_address)),
}?;

let host: String = match host {
Some(host) => host.to_string(),
None => "https://example.com".to_string(),
};

let parsed_url = match Url::parse(&host) {
Ok(host) => Ok(host),
Err(_) => Url::parse(&format!("https://{}", host)),
}?;

let scheme: &str = parsed_url.scheme();
if scheme != "http" && scheme != "https" {
failure::bail!("Your host scheme must be either http or https")
}
let is_https = scheme == "https";

let host = match parsed_url.host_str() {
Some(host_str) => Ok(host_str.to_string()),
None => Err(format_err!(
"Invalid host, accepted formats are http://example.com or example.com"
)),
}?;

let proxy = Proxy {
listening_address,
host,
is_https,
};

Ok(proxy)
}

pub fn start(
&self,
mut target: Target,
user: Option<GlobalUser>,
) -> Result<(), failure::Error> {
let https = HttpsConnector::new(4).expect("TLS initialization failed");
let client_main = Client::builder().build::<_, hyper::Body>(https);

let session = Uuid::new_v4().to_simple();
let verbose = true;
let sites_preview = false;
let script_id: String = upload(&mut target, user.as_ref(), sites_preview, verbose)?;
let host = self.host.clone();
let is_https = self.is_https.clone();
let listening_address_str = self
.listening_address
.to_string()
.replace("[::1]", "localhost");

// new_service is run for each connection, creating a 'service'
// to handle requests for that specific connection.
let new_service = move || {
let client = client_main.clone();
let script_id = script_id.clone();
let host = host.clone();
// This is the `Service` that will handle the connection.
// `service_fn_ok` is a helper to convert a function that
// returns a Response into a `Service`.
service_fn(move |mut req| {
let uri_path_and_query =
req.uri().path_and_query().map(|x| x.as_str()).unwrap_or("");
let uri_string = format!("https://{}{}", PREVIEW_HOST, uri_path_and_query);

let uri = uri_string.parse::<hyper::Uri>().unwrap();
let method = req.method().to_string();
let path = uri_path_and_query.to_string();

let now: DateTime<Local> = Local::now();
*req.uri_mut() = uri;
req.headers_mut().insert(
HeaderName::from_static("host"),
HeaderValue::from_static(PREVIEW_HOST),
);
let preview_id = HeaderValue::from_str(&format!(
"{}{}{}{}",
&script_id, session, is_https as u8, host
));

if let Ok(preview_id) = preview_id {
req.headers_mut()
.insert(HeaderName::from_static("cf-ew-preview"), preview_id);
println!(
"[{}] \"{} {}{} {:?}\"",
now.format("%Y-%m-%d %H:%M:%S"),
method,
host,
path,
req.version()
);
client.request(req)
} else {
client.request(req)
}
})
};

let server = Server::bind(&self.listening_address)
.serve(new_service)
.map_err(|e| eprintln!("server error: {}", e));

println!("Listening on http://{}", listening_address_str);

rt::run(server);
Ok(())
}
}

pub fn proxy(
mut target: Target,
target: Target,
user: Option<GlobalUser>,
host: Option<&str>,
port: Option<&str>,
ip: Option<&str>,
) -> Result<(), failure::Error> {
commands::build(&target)?;
let port: u16 = match port {
Some(port) => port.to_string().parse(),
None => Ok(3000),
}?;
let host: String = match host {
Some(host) => host.to_string(),
None => "https://example.com".to_string(),
};
let parsed_url = match Url::parse(&host) {
Ok(host) => Ok(host),
Err(_) => Url::parse(&format!("https://{}", host)),
}?;
let scheme: &str = parsed_url.scheme();
if scheme != "http" && scheme != "https" {
failure::bail!("Your host must be either http or https")
}
let is_https = scheme == "https";
let host_str: Option<&str> = parsed_url.host_str();
let host: Result<String, failure::Error> = if let Some(host_str) = host_str {
Ok(host_str.to_string())
} else {
failure::bail!("Invalid host, accepted formats are http://example.com or example.com")
};
let host = host?;
let listening_address = ([127, 0, 0, 1], port).into();

let https = HttpsConnector::new(4).expect("TLS initialization failed");
let client_main = Client::builder().build::<_, hyper::Body>(https);

let session = Uuid::new_v4().to_simple();
let verbose = true;
let sites_preview = false;
let script_id: String = upload(&mut target, user.as_ref(), sites_preview, verbose)?;

// new_service is run for each connection, creating a 'service'
// to handle requests for that specific connection.
let new_service = move || {
let client = client_main.clone();
let script_id = script_id.clone();
let host = host.clone();
// This is the `Service` that will handle the connection.
// `service_fn_ok` is a helper to convert a function that
// returns a Response into a `Service`.
service_fn(move |mut req| {
let uri_path_and_query = req.uri().path_and_query().map(|x| x.as_str()).unwrap_or("");
let uri_string = format!("https://{}{}", PREVIEW_HOST, uri_path_and_query);

let uri = uri_string.parse::<hyper::Uri>().unwrap();
let method = req.method().to_string();
let path = uri_path_and_query.to_string();

let now: DateTime<Local> = Local::now();
*req.uri_mut() = uri;
req.headers_mut().insert(
HeaderName::from_static("host"),
HeaderValue::from_static(PREVIEW_HOST),
);
let preview_id = HeaderValue::from_str(&format!(
"{}{}{}{}",
&script_id, session, is_https as u8, host
));

if let Ok(preview_id) = preview_id {
req.headers_mut()
.insert(HeaderName::from_static("cf-ew-preview"), preview_id);
println!(
"[{}] \"{} {}{} {:?}\"",
now.format("%Y-%m-%d %H:%M:%S"),
method,
host,
path,
req.version()
);
client.request(req)
} else {
client.request(req)
}
})
};

let server = Server::bind(&listening_address)
.serve(new_service)
.map_err(|e| eprintln!("server error: {}", e));

println!("Serving HTTP on http://{}", listening_address);

rt::run(server);
Ok(())
let proxy = Proxy::new(host, ip, port)?;
proxy.start(target, user)
}
12 changes: 10 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ fn run() -> Result<(), failure::Error> {
))
.arg(
Arg::with_name("port")
.help("port to listen on")
.help("port to listen on. defaults to 8000")
.short("p")
.long("port")
.takes_value(true)
Expand All @@ -364,6 +364,13 @@ fn run() -> Result<(), failure::Error> {
.short("h")
.long("host")
.takes_value(true)
)
.arg(
Arg::with_name("ip")
.help("ip to listsen on. defaults to localhost")
.short("i")
.long("ip")
.takes_value(true)
),
)
.subcommand(
Expand Down Expand Up @@ -538,11 +545,12 @@ fn run() -> Result<(), failure::Error> {
log::info!("Starting proxy service");
let port = matches.value_of("port");
let host = matches.value_of("host");
let ip = matches.value_of("ip");
let manifest = settings::target::Manifest::new(config_path)?;
let env = matches.value_of("env");
let target = manifest.get_target(env)?;
let user = settings::global_user::GlobalUser::new().ok();
commands::proxy(target, user, host, port)?;
commands::proxy(target, user, host, port, ip)?;
} else if matches.subcommand_matches("whoami").is_some() {
log::info!("Getting User settings");
let user = settings::global_user::GlobalUser::new()?;
Expand Down

0 comments on commit 5b721c4

Please sign in to comment.