use std::str::FromStr; use anyhow::Context; use anyhow::Error; use anyhow::Result; use actix_web::{get, web, App, HttpServer, Responder}; use derive_more::{Display, Error}; #[derive(Clone)] struct Endpoints { hello_service_endpoint: String, hello_service_port: u16, world_service_endpoint: String, world_service_port: u16, } #[derive(Debug, Display, Error)] #[display(fmt = "error: {}", inner)] struct AppError { inner: anyhow::Error, } impl actix_web::error::ResponseError for AppError {} #[get("/")] async fn joiner(state: web::Data) -> impl Responder { async fn inner(state: web::Data) -> Result { let hello = { let url = reqwest::Url::parse(&format!("http://{host}:{port}/", host = state.hello_service_endpoint, port = state.hello_service_port))?; reqwest::get(url) }; let world = { let url = reqwest::Url::parse(&format!("http://{host}:{port}/", host = state.world_service_endpoint, port = state.world_service_port))?; reqwest::get(url) }; let (hello, world) = tokio::try_join!(hello, world)?; let (hello, world) = tokio::try_join!(hello.text(), world.text())?; Ok(format!("{} {}", hello, world)) as Result } inner(state).await.map_err(|inner| AppError { inner }) } #[actix_web::main] async fn main() -> Result<()> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); let bind = std::env::var("HOST").expect("environment: HOST variable not set"); let port = std::env::var("PORT") .as_ref() .map(|p| u16::from_str(p)) .expect("environment: HOST variable not set") .context("Failed to parse port as integer")?; let hello_service = std::env::var("HELLO_SERVICE") .as_ref() .map(std::ops::Deref::deref) .map(parse_endpoint) .expect("environment: HELLO_SERVICE variable not set")?; let world_service = std::env::var("WORLD_SERVICE") .as_ref() .map(std::ops::Deref::deref) .map(parse_endpoint) .expect("environment: WORLD_SERVICE variable not set")?; HttpServer::new(move || { App::new() .app_data(web::Data::new(Endpoints { hello_service_endpoint: hello_service.0.clone(), hello_service_port: hello_service.1, world_service_endpoint: world_service.0.clone(), world_service_port: world_service.1, })) .service(joiner) }) .bind(format!("{}:{}", bind, port))? .run() .await .map_err(anyhow::Error::from) } fn parse_endpoint(s: &str) -> Result<(String, u16)> { let v = s.split(':').collect::>(); if v.len() != 2 { anyhow::bail!("Expected host:port, got: '{}'", s) } u16::from_str(v[1]) .map(|port| (v[0].to_string(), port)) .map_err(Error::from) }