use crate::config::models::{JokowayConfig, ServerConf};
use crate::extensions::dns::DnsExtension;
use crate::extensions::http::HttpExtension;
use crate::extensions::https::HttpsExtension;
use crate::prelude::core::*;
use crate::server::service::ServiceManager;
use crate::server::upstream::UpstreamExtension;
#[cfg(feature = "acme")]
use jokoway_acme::{AcmeConfigExt, AcmeExtension};
use jokoway_core::{AppContext, Context};
use pingora::server::Server;
use std::sync::Arc;
#[cfg(feature = "forwarded")]
use jokoway_forwarded::ForwardedExtension;
use pingora::server::configuration::Opt;
pub struct App {
pub config: JokowayConfig,
pub server_conf: Option<ServerConf>,
pub opt: Opt,
pub extensions: Vec<Box<dyn JokowayExtension>>,
pub app_ctx: AppContext,
}
impl App {
pub fn new(
config: JokowayConfig,
server_conf: Option<ServerConf>,
opt: Opt,
custom_extensions: Vec<Box<dyn JokowayExtension>>,
) -> Self {
let mut app = Self {
config,
server_conf,
opt,
extensions: custom_extensions,
app_ctx: AppContext::new(),
};
#[cfg(feature = "acme")]
if let Some(acme_settings) = app.config.acme() {
let acme_ext = AcmeExtension::new(&acme_settings);
app.add_extension(acme_ext);
}
app.add_extension(DnsExtension);
app.add_extension(UpstreamExtension);
app.add_extension(HttpExtension);
app.add_extension(HttpsExtension);
#[cfg(feature = "compress")]
{
use jokoway_compress::{
BrotliConfig, CompressExtension, CompressionConfig, CompressionConfigExt,
GzipConfig, ZstdConfig,
};
if let Some(compression_settings) = app.config.compression() {
let config = CompressionConfig {
min_size: compression_settings.min_size.unwrap_or(1024),
gzip: compression_settings.gzip.as_ref().map(|g| GzipConfig {
level: g.level.unwrap_or(6),
}),
#[cfg(feature = "compress-brotli")]
brotli: compression_settings.brotli.as_ref().map(|b| BrotliConfig {
quality: b.quality.unwrap_or(5),
lgwin: b.lgwin.unwrap_or(22),
buffer_size: b.buffer_size.unwrap_or(4096),
}),
#[cfg(feature = "compress-zstd")]
zstd: compression_settings.zstd.as_ref().map(|z| ZstdConfig {
level: z.level.unwrap_or(3),
}),
};
app.add_extension(CompressExtension::new(config));
}
}
#[cfg(feature = "api")]
{
if let Some(api_settings) = &app.config.api {
if api_settings.listen.trim().is_empty() {
log::warn!("API configured but listen address is empty; skipping API server");
} else {
app.add_extension(crate::extensions::api::ApiExtension::new(
api_settings.clone(),
));
}
}
}
#[cfg(feature = "forwarded")]
{
app.add_extension(ForwardedExtension);
}
app
}
pub fn add_extension<E: JokowayExtension + 'static>(&mut self, extension: E) {
self.extensions.push(Box::new(extension));
}
pub fn app_ctx(&self) -> &AppContext {
&self.app_ctx
}
pub fn build(mut self) -> Result<Server, crate::error::JokowayError> {
let mut server =
Server::new_with_opt_and_conf(Some(self.opt), self.server_conf.unwrap_or_default());
server.bootstrap();
let mut app_ctx = self.app_ctx;
let config_arc = Arc::new(self.config.clone());
app_ctx.insert(self.config.clone());
let tls_callback = TlsCallback::new();
app_ctx.insert(tls_callback);
let service_manager = ServiceManager::new(config_arc.clone())?;
app_ctx.insert(service_manager);
self.extensions
.sort_by_key(|e| std::cmp::Reverse(e.order()));
let mut middlewares: Vec<Arc<dyn JokowayMiddlewareDyn>> = Vec::new();
for i in 0..self.extensions.len() {
self.extensions[i].init(&mut server, &mut app_ctx, &mut middlewares)?;
middlewares.sort_by_key(|b| std::cmp::Reverse(b.order()));
}
Ok(server)
}
pub fn run(self) -> Result<(), crate::error::JokowayError> {
let server = self.build()?;
server.run_forever();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::core::JokowayMiddleware;
use async_trait::async_trait;
struct EarlyMiddleware;
#[async_trait]
impl JokowayMiddleware for EarlyMiddleware {
type CTX = ();
fn name(&self) -> &'static str {
"EarlyMiddleware"
}
fn new_ctx(&self) -> Self::CTX {}
fn order(&self) -> i16 {
10
}
}
struct DefaultMiddleware;
#[async_trait]
impl JokowayMiddleware for DefaultMiddleware {
type CTX = ();
fn name(&self) -> &'static str {
"DefaultMiddleware"
}
fn new_ctx(&self) -> Self::CTX {}
}
struct LateMiddleware;
#[async_trait]
impl JokowayMiddleware for LateMiddleware {
type CTX = ();
fn name(&self) -> &'static str {
"LateMiddleware"
}
fn new_ctx(&self) -> Self::CTX {}
fn order(&self) -> i16 {
-10
}
}
#[test]
fn test_middleware_ordering() {
let mut middlewares: Vec<Arc<dyn JokowayMiddlewareDyn>> = vec![
Arc::new(LateMiddleware),
Arc::new(EarlyMiddleware),
Arc::new(DefaultMiddleware),
];
middlewares.sort_by_key(|m| -m.order());
assert_eq!(middlewares.len(), 3);
assert_eq!(middlewares[0].order(), 10); assert_eq!(middlewares[1].order(), 0); assert_eq!(middlewares[2].order(), -10); }
#[test]
fn test_middleware_ordering_same_order() {
let mut middlewares: Vec<Arc<dyn JokowayMiddlewareDyn>> =
vec![Arc::new(DefaultMiddleware), Arc::new(DefaultMiddleware)];
middlewares.sort_by_key(|b| std::cmp::Reverse(b.order()));
assert_eq!(middlewares.len(), 2);
assert_eq!(middlewares[0].order(), 0);
assert_eq!(middlewares[1].order(), 0);
}
#[test]
fn test_middleware_insertion_order_preserved() {
struct FirstMiddleware;
#[async_trait]
impl JokowayMiddleware for FirstMiddleware {
type CTX = String;
fn name(&self) -> &'static str {
"FirstMiddleware"
}
fn new_ctx(&self) -> Self::CTX {
"first".to_string()
}
fn order(&self) -> i16 {
0
}
}
struct SecondMiddleware;
#[async_trait]
impl JokowayMiddleware for SecondMiddleware {
type CTX = String;
fn name(&self) -> &'static str {
"SecondMiddleware"
}
fn new_ctx(&self) -> Self::CTX {
"second".to_string()
}
fn order(&self) -> i16 {
0
}
}
struct ThirdMiddleware;
#[async_trait]
impl JokowayMiddleware for ThirdMiddleware {
type CTX = String;
fn name(&self) -> &'static str {
"ThirdMiddleware"
}
fn new_ctx(&self) -> Self::CTX {
"third".to_string()
}
fn order(&self) -> i16 {
0
}
}
let mut middlewares: Vec<Arc<dyn JokowayMiddlewareDyn>> = vec![
Arc::new(FirstMiddleware),
Arc::new(SecondMiddleware),
Arc::new(ThirdMiddleware),
];
middlewares.sort_by_key(|b| std::cmp::Reverse(b.order()));
assert_eq!(middlewares.len(), 3);
assert_eq!(middlewares[0].order(), 0);
assert_eq!(middlewares[1].order(), 0);
assert_eq!(middlewares[2].order(), 0);
let ctx0 = middlewares[0].new_ctx_dyn();
let ctx1 = middlewares[1].new_ctx_dyn();
let ctx2 = middlewares[2].new_ctx_dyn();
assert_eq!(ctx0.downcast_ref::<String>().unwrap(), "first");
assert_eq!(ctx1.downcast_ref::<String>().unwrap(), "second");
assert_eq!(ctx2.downcast_ref::<String>().unwrap(), "third");
}
#[test]
fn test_extension_ordering() {
use crate::prelude::core::JokowayExtension;
struct OrderedExtension {
order: i16,
}
impl JokowayExtension for OrderedExtension {
fn order(&self) -> i16 {
self.order
}
}
let mut extensions: Vec<Box<dyn JokowayExtension>> = vec![
Box::new(OrderedExtension { order: 10 }),
Box::new(OrderedExtension { order: 0 }),
Box::new(OrderedExtension { order: -10 }),
];
extensions.sort_by_key(|e| std::cmp::Reverse(e.order()));
assert_eq!(extensions[0].order(), 10);
assert_eq!(extensions[1].order(), 0);
assert_eq!(extensions[2].order(), -10);
}
#[test]
fn test_extension_returns_middleware() {
use crate::prelude::core::JokowayExtension;
use crate::server::app::App;
use pingora::server::configuration::Opt;
struct MwExtension;
#[async_trait]
impl JokowayExtension for MwExtension {
fn init(
&self,
_server: &mut pingora::server::Server,
_app_ctx: &mut AppContext,
middlewares: &mut Vec<std::sync::Arc<dyn JokowayMiddlewareDyn>>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mw = DefaultMiddleware;
middlewares.push(Arc::new(mw));
Ok(())
}
}
struct VerifierExtension;
impl JokowayExtension for VerifierExtension {
fn order(&self) -> i16 {
-10 }
fn init(
&self,
_server: &mut pingora::server::Server,
_app_ctx: &mut AppContext,
middlewares: &mut Vec<std::sync::Arc<dyn JokowayMiddlewareDyn>>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
assert!(middlewares.iter().any(|m| m.name() == "DefaultMiddleware"));
Ok(())
}
}
let app = App::new(
JokowayConfig::default(),
None,
Opt::default(),
vec![Box::new(MwExtension), Box::new(VerifierExtension)],
);
let _server = app.build().unwrap();
}
}