From 57e18abb8340ba3ad2ff9c4c80b2c0dd33aa4313 Mon Sep 17 00:00:00 2001 From: Andreas Brett Date: Sat, 4 Apr 2026 21:00:37 +0000 Subject: [PATCH 1/3] send empty body for _ping endpoint --- src/proxy.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/proxy.rs b/src/proxy.rs index 6bc8fb7..465dcc5 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -37,6 +37,11 @@ pub async fn forward( .await .map_err(web::Error::from)?; + // respond without body for /_ping HEAD request + if req.uri().path() == "/_ping" && req.method() == http::Method::HEAD { + return Ok(web::HttpResponse::build(res.status()).finish()); + } + let mut client_resp = web::HttpResponse::build(res.status()); client_resp.content_type(res.content_type()); From d51533cda79bababf8af54c8d4f7f900cbfea287 Mon Sep 17 00:00:00 2001 From: Andreas Brett Date: Sat, 4 Apr 2026 21:36:39 +0000 Subject: [PATCH 2/3] send cloned headers --- src/proxy.rs | 108 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 69 insertions(+), 39 deletions(-) diff --git a/src/proxy.rs b/src/proxy.rs index 465dcc5..96785fa 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -1,11 +1,14 @@ -use tracing::*; -use std::sync::{Arc, Mutex}; -use std::collections::HashMap; -use ntex::{http::{self, HttpMessage}, web::{self}}; -use futures_util::TryStreamExt; use ::http::StatusCode; +use futures_util::TryStreamExt; +use ntex::{ + http::{self, HttpMessage}, + web::{self}, +}; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use tracing::*; -use crate::config::{AppConfig}; +use crate::config::AppConfig; use crate::docker::{self, types::*}; use crate::utils; @@ -26,7 +29,7 @@ pub async fn forward( client: web::types::State, forward_url: web::types::State, app_config: web::types::State, - data: web::types::State + data: web::types::State, ) -> Result { let mut new_url = forward_url.get_ref().clone(); new_url.set_path(req.uri().path()); @@ -37,51 +40,66 @@ pub async fn forward( .await .map_err(web::Error::from)?; + let mut client_resp = web::HttpResponse::build(res.status()); + // respond without body for /_ping HEAD request if req.uri().path() == "/_ping" && req.method() == http::Method::HEAD { - return Ok(web::HttpResponse::build(res.status()).finish()); + for (key, value) in res.headers().iter() { + client_resp.header(key.clone(), value.clone()); + } + return Ok(client_resp.finish()); } - let mut client_resp = web::HttpResponse::build(res.status()); client_resp.content_type(res.content_type()); if res.status() == 200 { - // if route is to list containers we want to return a filtered list if new_url.path().contains("containers/json") { let _list_span = span!(Level::DEBUG, "Container List").entered(); - let container_res = &res.json::>() - // 2mb in bytes - .limit(2097152).await; + let container_res = &res + .json::>() + // 2mb in bytes + .limit(2097152) + .await; let containers = match container_res { - Ok(list_res) => { - list_res - } + Ok(list_res) => list_res, Err(e) => { panic!("{e}"); } }; - + // filter all containers to only those that have values from CONTAINER_NAMES includes in their names - let filtered_containers = containers.into_iter() - .filter(|&con| { - let _list_span = span!(Level::DEBUG, "Container", id = utils::short_id(con.id.as_ref().unwrap())).entered(); - docker::container_summary_match(con, &app_config.get_ref().container_names, &app_config.get_ref().container_labels) + let filtered_containers = containers + .into_iter() + .filter(|&con| { + let _list_span = span!( + Level::DEBUG, + "Container", + id = utils::short_id(con.id.as_ref().unwrap()) + ) + .entered(); + docker::container_summary_match( + con, + &app_config.get_ref().container_names, + &app_config.get_ref().container_labels, + ) }) .collect::>(); let fresp = web::HttpResponse::build(res.status()).json(&filtered_containers); - debug!("{} of {} containers valid", filtered_containers.len(), containers.len()); + debug!( + "{} of {} containers valid", + filtered_containers.len(), + containers.len() + ); Ok(fresp) } else { - // only deal with routes that are for containers like /containers/1234/{some_resource} match docker::match_container_get(new_url.path()) { // the regex pulls the container id from the route with a named capture group, avaiable as m.id - - Some (m) => { + Some(m) => { let short_cid = utils::short_id(&m.id); //format!("{start}...{end}", start = &m.id[..6], end = &m.id[&m.id.len() - 6..]); let _con_span = span!(Level::DEBUG, "Container", id = short_cid).entered(); debug!("Matched container route with Id {}", &m.id); @@ -93,11 +111,21 @@ pub async fn forward( // or if we encountered an error last time then try again if !cm.contains_key(&m.id) || cm.get(&m.id).unwrap().is_none() { debug!("Requested Id not in map, trying to inspect..."); - let info_res = docker::get_container_info(&client, &forward_url, &m.id).await; + let info_res = + docker::get_container_info(&client, &forward_url, &m.id).await; match info_res { Ok((name, labels)) => { - let is_container_match = docker::match_labels_or_names(&app_config.get_ref().container_names, &app_config.get_ref().container_labels, &Vec::from([name.clone()]), &labels); - debug!("Recording container '{}' {} valid", name, is!(is_container_match; "as";"as not")); + let is_container_match = docker::match_labels_or_names( + &app_config.get_ref().container_names, + &app_config.get_ref().container_labels, + &Vec::from([name.clone()]), + &labels, + ); + debug!( + "Recording container '{}' {} valid", + name, + is!(is_container_match; "as";"as not") + ); cm.insert(m.id.clone(), Some(is_container_match)); } Err(e) => { @@ -114,7 +142,7 @@ pub async fn forward( // then we try to get the name from the stateful hashmap let allowed = cm.get(&m.id).unwrap(); - + match allowed { Some(n) => { // only return if a response if requested container has a name that includes values from CONTAINER_NAMES @@ -123,17 +151,16 @@ pub async fn forward( // if the route resource is specifically the Container Inspect API // then we may need to scrub Envs if SCRUB_ENVS=true if m.resource == "json" { - client_resp.content_type("application/json"); if app_config.get_ref().scrub_envs { - let mut container = res.json::().await.unwrap(); + let mut container = + res.json::().await.unwrap(); container.config.as_mut().unwrap().env = Some(Vec::new()); Ok(client_resp.json(&container)) } else { Ok(client_resp.streaming(res.into_stream())) } - } else { client_resp.content_type(res.content_type()); Ok(client_resp.streaming(res.into_stream())) @@ -141,13 +168,19 @@ pub async fn forward( } else { debug!("Does not match container filters, 404ing..."); client_resp.status(StatusCode::NOT_FOUND); - Ok(client_resp.json(&DockerErrorMessage { message: format!("No such container: {}", &m.id)})) + Ok(client_resp.json(&DockerErrorMessage { + message: format!("No such container: {}", &m.id), + })) } } None => { - debug!("Does not exist or Docker API previously returned an error, 404ing..."); + debug!( + "Does not exist or Docker API previously returned an error, 404ing..." + ); client_resp.status(StatusCode::NOT_FOUND); - Ok(client_resp.json(&DockerErrorMessage { message: format!("No such container: {}", &m.id)})) + Ok(client_resp.json(&DockerErrorMessage { + message: format!("No such container: {}", &m.id), + })) } } } @@ -158,11 +191,8 @@ pub async fn forward( } } } - } else { let stream = res.into_stream(); Ok(client_resp.streaming(stream)) } - - -} \ No newline at end of file +} From 1a5ab3c86bac35df144d0b421feaec357219cdda Mon Sep 17 00:00:00 2001 From: Andreas Brett Date: Sat, 4 Apr 2026 21:49:56 +0000 Subject: [PATCH 3/3] remove formatting changes by Github Codespace --- src/proxy.rs | 100 +++++++++++++++++++-------------------------------- 1 file changed, 37 insertions(+), 63 deletions(-) diff --git a/src/proxy.rs b/src/proxy.rs index 96785fa..1e1fc08 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -1,14 +1,11 @@ -use ::http::StatusCode; -use futures_util::TryStreamExt; -use ntex::{ - http::{self, HttpMessage}, - web::{self}, -}; -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; use tracing::*; +use std::sync::{Arc, Mutex}; +use std::collections::HashMap; +use ntex::{http::{self, HttpMessage}, web::{self}}; +use futures_util::TryStreamExt; +use ::http::StatusCode; -use crate::config::AppConfig; +use crate::config::{AppConfig}; use crate::docker::{self, types::*}; use crate::utils; @@ -29,7 +26,7 @@ pub async fn forward( client: web::types::State, forward_url: web::types::State, app_config: web::types::State, - data: web::types::State, + data: web::types::State ) -> Result { let mut new_url = forward_url.get_ref().clone(); new_url.set_path(req.uri().path()); @@ -53,53 +50,42 @@ pub async fn forward( client_resp.content_type(res.content_type()); if res.status() == 200 { + // if route is to list containers we want to return a filtered list if new_url.path().contains("containers/json") { let _list_span = span!(Level::DEBUG, "Container List").entered(); - let container_res = &res - .json::>() - // 2mb in bytes - .limit(2097152) - .await; + let container_res = &res.json::>() + // 2mb in bytes + .limit(2097152).await; let containers = match container_res { - Ok(list_res) => list_res, + Ok(list_res) => { + list_res + } Err(e) => { panic!("{e}"); } }; - + // filter all containers to only those that have values from CONTAINER_NAMES includes in their names - let filtered_containers = containers - .into_iter() - .filter(|&con| { - let _list_span = span!( - Level::DEBUG, - "Container", - id = utils::short_id(con.id.as_ref().unwrap()) - ) - .entered(); - docker::container_summary_match( - con, - &app_config.get_ref().container_names, - &app_config.get_ref().container_labels, - ) + let filtered_containers = containers.into_iter() + .filter(|&con| { + let _list_span = span!(Level::DEBUG, "Container", id = utils::short_id(con.id.as_ref().unwrap())).entered(); + docker::container_summary_match(con, &app_config.get_ref().container_names, &app_config.get_ref().container_labels) }) .collect::>(); let fresp = web::HttpResponse::build(res.status()).json(&filtered_containers); - debug!( - "{} of {} containers valid", - filtered_containers.len(), - containers.len() - ); + debug!("{} of {} containers valid", filtered_containers.len(), containers.len()); Ok(fresp) } else { + // only deal with routes that are for containers like /containers/1234/{some_resource} match docker::match_container_get(new_url.path()) { // the regex pulls the container id from the route with a named capture group, avaiable as m.id - Some(m) => { + + Some (m) => { let short_cid = utils::short_id(&m.id); //format!("{start}...{end}", start = &m.id[..6], end = &m.id[&m.id.len() - 6..]); let _con_span = span!(Level::DEBUG, "Container", id = short_cid).entered(); debug!("Matched container route with Id {}", &m.id); @@ -111,21 +97,11 @@ pub async fn forward( // or if we encountered an error last time then try again if !cm.contains_key(&m.id) || cm.get(&m.id).unwrap().is_none() { debug!("Requested Id not in map, trying to inspect..."); - let info_res = - docker::get_container_info(&client, &forward_url, &m.id).await; + let info_res = docker::get_container_info(&client, &forward_url, &m.id).await; match info_res { Ok((name, labels)) => { - let is_container_match = docker::match_labels_or_names( - &app_config.get_ref().container_names, - &app_config.get_ref().container_labels, - &Vec::from([name.clone()]), - &labels, - ); - debug!( - "Recording container '{}' {} valid", - name, - is!(is_container_match; "as";"as not") - ); + let is_container_match = docker::match_labels_or_names(&app_config.get_ref().container_names, &app_config.get_ref().container_labels, &Vec::from([name.clone()]), &labels); + debug!("Recording container '{}' {} valid", name, is!(is_container_match; "as";"as not")); cm.insert(m.id.clone(), Some(is_container_match)); } Err(e) => { @@ -142,7 +118,7 @@ pub async fn forward( // then we try to get the name from the stateful hashmap let allowed = cm.get(&m.id).unwrap(); - + match allowed { Some(n) => { // only return if a response if requested container has a name that includes values from CONTAINER_NAMES @@ -151,16 +127,17 @@ pub async fn forward( // if the route resource is specifically the Container Inspect API // then we may need to scrub Envs if SCRUB_ENVS=true if m.resource == "json" { + client_resp.content_type("application/json"); if app_config.get_ref().scrub_envs { - let mut container = - res.json::().await.unwrap(); + let mut container = res.json::().await.unwrap(); container.config.as_mut().unwrap().env = Some(Vec::new()); Ok(client_resp.json(&container)) } else { Ok(client_resp.streaming(res.into_stream())) } + } else { client_resp.content_type(res.content_type()); Ok(client_resp.streaming(res.into_stream())) @@ -168,19 +145,13 @@ pub async fn forward( } else { debug!("Does not match container filters, 404ing..."); client_resp.status(StatusCode::NOT_FOUND); - Ok(client_resp.json(&DockerErrorMessage { - message: format!("No such container: {}", &m.id), - })) + Ok(client_resp.json(&DockerErrorMessage { message: format!("No such container: {}", &m.id)})) } } None => { - debug!( - "Does not exist or Docker API previously returned an error, 404ing..." - ); + debug!("Does not exist or Docker API previously returned an error, 404ing..."); client_resp.status(StatusCode::NOT_FOUND); - Ok(client_resp.json(&DockerErrorMessage { - message: format!("No such container: {}", &m.id), - })) + Ok(client_resp.json(&DockerErrorMessage { message: format!("No such container: {}", &m.id)})) } } } @@ -191,8 +162,11 @@ pub async fn forward( } } } + } else { let stream = res.into_stream(); Ok(client_resp.streaming(stream)) } -} + + +} \ No newline at end of file