use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Client, Request, Response, Server, StatusCode};
use std::env;
use std::net::SocketAddr;
use toml;
#[macro_use]
extern crate log;
use es_public_proxy::{filter_request, ProxyConfig};
async fn upstream_req(
req: Request
,
config: ProxyConfig,
) -> Result, hyper::Error> {
info!("hit: {}", req.uri());
let parsed = filter_request(req, &config).await;
let resp = match parsed {
Ok(upstream_req) => {
debug!("sending request...");
Client::new().request(upstream_req).await?
}
Err(other) => Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.header("Content-Type", "application/json; charset=UTF-8")
.body(serde_json::to_string(&other.to_json()).unwrap().into())
.unwrap(),
};
debug!("resp!");
Ok(resp)
}
async fn shutdown_signal() {
// Wait for the CTRL+C signal
tokio::signal::ctrl_c()
.await
.expect("failed to install CTRL+C signal handler");
}
async fn run_server(config: ProxyConfig) {
let addr = match &config.bind_addr {
None => SocketAddr::from(([127, 0, 0, 1], 9292)),
Some(addr) => addr.parse().unwrap(),
};
warn!("Listening on http://{}", addr);
// TODO: possible to avoid cloning config on every connection?
let make_svc = make_service_fn(move |_| {
let inner = config.clone();
async move { Ok::<_, hyper::Error>(service_fn(move |req| upstream_req(req, inner.clone()))) }
});
let serve_future = Server::bind(&addr).serve(make_svc);
let graceful = serve_future.with_graceful_shutdown(shutdown_signal());
if let Err(e) = graceful.await {
error!("server error: {}", e);
}
}
fn usage() -> String {
"es-public-proxy [--config CONFIG_FILE] [--help]".to_string()
}
fn load_config() -> ProxyConfig {
let args: Vec = env::args().collect();
let args: Vec<&str> = args.iter().map(|x| x.as_str()).collect();
let mut config_path: Option = None;
let mut allow_all_indices = false;
// first parse CLI arg
match args.as_slice() {
[_] | [] => {}
[_, "-h"] | [_, "--help"] => {
println!("{}", usage());
std::process::exit(0);
}
[_, "--example-config"] => {
println!("{}", include_str!("../example_config.toml"));
std::process::exit(0);
}
[_, "--config", p] => config_path = Some(p.to_string()),
[_, "--allow-all-indices"] => allow_all_indices = true,
_ => {
eprintln!("{}", usage());
eprintln!("couldn't parse arguments");
std::process::exit(1);
}
}
// then try environment variables
if let None = config_path {
config_path = std::env::var("ES_PUBLIC_PROXY_CONFIG_PATH").ok();
}
// then either load config file (TOML), or use default config
let mut config = if let Some(config_path) = config_path {
let config_toml = std::fs::read_to_string(config_path).unwrap();
let config: ProxyConfig = toml::from_str(&config_toml).unwrap();
config
} else {
ProxyConfig::default()
};
if allow_all_indices {
config.allow_all_indices = Some(true);
}
config
}
#[tokio::main]
async fn main() {
let config = load_config();
env_logger::init_from_env(
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
);
run_server(config).await;
}