diff --git a/Cargo.lock b/Cargo.lock index a75c5096..ac3641d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -330,6 +330,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "chrono" version = "0.4.37" @@ -1386,6 +1392,18 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1580,6 +1598,7 @@ dependencies = [ "log", "memory-stats", "mime_guess", + "nix 0.28.0", "num_cpus", "once_cell", "path-absolutize", @@ -1663,7 +1682,7 @@ dependencies = [ "libc", "log", "lru", - "nix", + "nix 0.24.3", "once_cell", "openssl-probe", "parking_lot", diff --git a/Cargo.toml b/Cargo.toml index acc58456..1acebe0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ humantime-serde = "1.1.1" log = "0.4.21" memory-stats = { version = "1.1.0", features = ["always_use_statm"] } mime_guess = "2.0.4" +nix = { version = "0.28.0", features = ["signal"] } num_cpus = "1.16.0" once_cell = "1.19.0" path-absolutize = "3.1.1" diff --git a/README.md b/README.md index 0ab757c7..466a740c 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ A reverse proxy like nginx, built on [pingora](https://github.com/cloudflare/pin ## Feature - Filter location by host and path +- static file serve - HTTP 1/2 end to end proxy - Graceful reload - Template for http access log @@ -17,7 +18,7 @@ A reverse proxy like nginx, built on [pingora](https://github.com/cloudflare/pin Loads all configurations from `/opt/proxy` and run in the background. Log appends to `/opt/proxy/pingap.log`. ```bash -RUST_LOG=INFO pingap -c=/opt/proxy -d --log=/opt/proxy/pingap.log +RUST_LOG=INFO pingap -c=/opt/proxy/pingap.toml -d --log=/opt/proxy/pingap.log ``` ## Graceful restart @@ -25,9 +26,9 @@ RUST_LOG=INFO pingap -c=/opt/proxy -d --log=/opt/proxy/pingap.log Validate the configurations, send quit signal to pingap, then start a new process to handle all requests. ```bash -RUST_LOG=INFO pingap -c=/opt/proxy -t \ +RUST_LOG=INFO pingap -c=/opt/proxy/pingap.toml -t \ && pkill -SIGQUIT pingap \ - && RUST_LOG=INFO pingap -c=/opt/proxy -d -u --log=/opt/proxy/pingap.log + && RUST_LOG=INFO pingap -c=/opt/proxy/pingap.toml -d -u --log=/opt/proxy/pingap.log ``` ## Config diff --git a/TODO.md b/TODO.md index 7c4b6598..9f7d3f67 100644 --- a/TODO.md +++ b/TODO.md @@ -23,5 +23,5 @@ - [x] add remark for config - [x] support multi host for location? - [ ] support set upstream_keepalive_pool_size -- [ ] graceful restart for admin web +- [x] graceful restart for admin web - [ ] use stable pingora diff --git a/docs/introduction_zh.md b/docs/introduction_zh.md index aa5d52bf..7657bf20 100644 --- a/docs/introduction_zh.md +++ b/docs/introduction_zh.md @@ -4,14 +4,12 @@ description: Pingap 简述 Pingap是基于[pingora](https://github.com/cloudflare/pingora)开发的,pingora提供了各类模块便于rust开发者使用,但并不方便非rust开发者使用,因此pingap提供了以toml的形式配置简单易用的反向代理,实现支持多location代理转发。特性如下: -- 可通过请求的路径与域名筛选对应的location -- 支持静态文件目录处理 -- 支持mock的响应配置 -- 支持HTTP1与HTTP2 -- 无中断请求的配置更新 -- 模板式的请求日志输出 - -TODO 接入http缓存的逻辑 +- 支持多location配置,可通过请求的路径与域名筛选 +- 支持静态文件目录处理,简单方便的chunk的形式响应静态文件 +- 支持mock的响应配置,方便测试或应急使用 +- 支持HTTP1与HTTP2两种协议 +- 无中断请求的配置更新,方便实时更新应用配置 +- 模板式的请求日志输出,可按模板指定各种输出 [Pingap处理流程](./phase_chart_zh.md) diff --git a/src/config/mod.rs b/src/config/mod.rs index d5a9b23d..588cda49 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,5 +1,10 @@ +use log::info; use once_cell::sync::Lazy; use once_cell::sync::OnceCell; +use std::io; +use std::path::PathBuf; +use std::process; +use std::process::Command; use std::time::{Duration, SystemTime, UNIX_EPOCH}; mod load; @@ -24,3 +29,41 @@ static START_TIME: Lazy = Lazy::new(|| { pub fn get_start_time() -> u64 { START_TIME.as_secs() } + +#[derive(Debug, Default)] +pub struct RestartProcessCommand { + pub exec_path: PathBuf, + pub log_level: String, + pub args: Vec, +} + +impl RestartProcessCommand { + fn exec(&self) -> io::Result { + Command::new(&self.exec_path) + .env("RUST_LOG", &self.log_level) + .args(&self.args) + .output() + } +} + +static CMD: OnceCell = OnceCell::new(); + +pub fn set_restart_process_command(data: RestartProcessCommand) { + CMD.get_or_init(|| data); +} + +pub fn restart() -> io::Result { + info!("pingap will restart now"); + if let Some(cmd) = CMD.get() { + nix::sys::signal::kill( + nix::unistd::Pid::from_raw(std::process::id() as i32), + nix::sys::signal::SIGQUIT, + )?; + cmd.exec() + } else { + Err(std::io::Error::new( + io::ErrorKind::NotFound, + "Command not found", + )) + } +} diff --git a/src/main.rs b/src/main.rs index 65f51d3d..4b4024fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -90,6 +90,28 @@ fn run() -> Result<(), Box> { } config::set_config_path(&args.conf); + if let Ok(exec_path) = std::env::current_exe() { + let mut cmd = config::RestartProcessCommand { + exec_path, + ..Default::default() + }; + if let Ok(env) = std::env::var("RUST_LOG") { + cmd.log_level = env; + } + let conf_path = utils::resolve_path(&args.conf); + + let mut new_args = vec![ + format!("-c={conf_path}"), + "-d".to_string(), + "-u".to_string(), + ]; + if let Some(log) = &args.log { + new_args.push(format!("--log={log}")); + } + cmd.args = new_args; + config::set_restart_process_command(cmd); + } + let opt = Opt { upgrade: args.upgrade, daemon: args.daemon, diff --git a/src/serve/admin.rs b/src/serve/admin.rs index 15680a66..279ae4ab 100644 --- a/src/serve/admin.rs +++ b/src/serve/admin.rs @@ -206,6 +206,12 @@ impl Serve for AdminServe { memory, }) .unwrap_or(HttpResponse::unknown_error()) + } else if path == "/restart" { + if let Err(e) = config::restart() { + error!("Restart fail: {e}"); + return Err(pingora::Error::new_str("restart fail")); + } + HttpResponse::no_content() } else { let mut file = path.substring(1, path.len()); if file.is_empty() { diff --git a/web/src/components/main-header.tsx b/web/src/components/main-header.tsx index 9ffca557..a7e168f7 100644 --- a/web/src/components/main-header.tsx +++ b/web/src/components/main-header.tsx @@ -8,10 +8,15 @@ import IconButton from "@mui/material/IconButton"; import SettingsSuggestIcon from "@mui/icons-material/SettingsSuggest"; import SwipeableDrawer from "@mui/material/SwipeableDrawer"; import CardContent from "@mui/material/CardContent"; -import Divider from "@mui/material/Divider"; +import Button from "@mui/material/Button"; import Card from "@mui/material/Card"; - +import Dialog from "@mui/material/Dialog"; +import DialogActions from "@mui/material/DialogActions"; +import DialogContent from "@mui/material/DialogContent"; +import DialogContentText from "@mui/material/DialogContentText"; +import DialogTitle from "@mui/material/DialogTitle"; import useBasicStore from "../states/basic"; +import request from "../helpers/request"; export default function MainHeader() { const [fetch] = useBasicStore((state) => [state.fetch]); @@ -19,6 +24,7 @@ export default function MainHeader() { const [version, setVersion] = React.useState(""); const [memory, setMemory] = React.useState(""); const [showSetting, setShowSetting] = React.useState(false); + const [showRestartDialog, setShowRestartDialog] = React.useState(false); useAsync(async () => { try { @@ -30,6 +36,16 @@ export default function MainHeader() { console.error(err); } }, []); + + const confirmRestart = async () => { + try { + await request.post("/restart"); + setShowRestartDialog(false); + } catch (err) { + console.error(err); + alert(err); + } + }; const box = ( Memory: {memory} + + { + setShowRestartDialog(false); + }} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + + {"Are you sure to restart pingap?"} + + + + Pingap will graceful restart with new configuration. + + + + + + + ); return (