use super::{Error, get_hash_key, get_plugin_factory, get_str_conf};
use async_trait::async_trait;
use bytes::Bytes;
use ctor::ctor;
use http::StatusCode;
use pingap_config::PluginConf;
use pingap_core::{Ctx, HttpResponse, Plugin, PluginStep, RequestPluginResult};
use pingora::proxy::Session;
use std::borrow::Cow;
use std::sync::Arc;
use std::sync::LazyLock;
use tracing::debug;
type Result<T, E = Error> = std::result::Result<T, E>;
pub struct Ping {
path: String,
plugin_step: PluginStep,
hash_value: String,
}
static PONG_RESPONSE: LazyLock<HttpResponse> = LazyLock::new(|| HttpResponse {
status: StatusCode::OK,
body: Bytes::from_static(b"pong"),
..Default::default()
});
impl Ping {
pub fn new(params: &PluginConf) -> Result<Self> {
debug!(params = params.to_string(), "new ping plugin");
let hash_value = get_hash_key(params);
Ok(Self {
hash_value,
path: get_str_conf(params, "path"),
plugin_step: PluginStep::Request,
})
}
}
#[async_trait]
impl Plugin for Ping {
#[inline]
fn config_key(&self) -> Cow<'_, str> {
Cow::Borrowed(&self.hash_value)
}
#[inline]
async fn handle_request(
&self,
step: PluginStep,
session: &mut Session,
_ctx: &mut Ctx,
) -> pingora::Result<RequestPluginResult> {
if step != self.plugin_step {
return Ok(RequestPluginResult::Skipped);
}
if session.req_header().uri.path() == self.path {
return Ok(RequestPluginResult::Respond(PONG_RESPONSE.clone()));
}
Ok(RequestPluginResult::Continue)
}
}
#[ctor]
fn init() {
get_plugin_factory()
.register("ping", |params| Ok(Arc::new(Ping::new(params)?)));
}
#[cfg(test)]
mod tests {
use super::*;
use pingap_config::PluginConf;
use pingap_core::{Ctx, PluginStep};
use pingora::proxy::Session;
use pretty_assertions::assert_eq;
use tokio_test::io::Builder;
#[tokio::test]
async fn test_ping() {
let ping = Ping::new(
&toml::from_str::<PluginConf>(
r###"
path = "/ping"
"###,
)
.unwrap(),
)
.unwrap();
assert_eq!("request", ping.plugin_step.to_string());
assert_eq!("/ping", ping.path);
let headers = [""].join("\r\n");
let input_header = format!("GET /ping HTTP/1.1\r\n{headers}\r\n\r\n");
let mock_io = Builder::new().read(input_header.as_bytes()).build();
let mut session = Session::new_h1(Box::new(mock_io));
session.read_request().await.unwrap();
let result = ping
.handle_request(
PluginStep::Request,
&mut session,
&mut Ctx::default(),
)
.await
.unwrap();
let RequestPluginResult::Respond(resp) = result else {
panic!("result is not Respond");
};
assert_eq!(200, resp.status.as_u16());
assert_eq!(b"pong", resp.body.as_ref());
}
}