Skip to content

Commit

Permalink
Dynamic automatic answers system
Browse files Browse the repository at this point in the history
- Every default answers is configurable
- Default answers are stored as Templates to allow dynamic content
- Tamplates are a wrapper around a Kawa stream which buffer is shared
  with an Rc
- 301 answers are treated like all other default answers
- response_stream in HTTP state can be either a generic Kawa stream from
  a backend, or a templated stream from a default answer
- Setting a default answer takes structured fields which will be
  forwarded to the Template

TODOs:
- Override default answers with html files from config
- Format sozu details in templates variables to be human readable
- Update Kawa (the Templates requires new features and fixes)

Signed-off-by: Eloi DEMOLIS <[email protected]>
  • Loading branch information
Wonshtrum committed Feb 22, 2024
1 parent 129c0d7 commit b0fa2f0
Show file tree
Hide file tree
Showing 5 changed files with 843 additions and 325 deletions.
44 changes: 31 additions & 13 deletions lib/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ use crate::{
pool::Pool,
protocol::{
http::{
answers::HttpAnswers,
answers::{HttpAnswers, RawAnswers},
parser::{hostname_and_port, Method},
ResponseStream,
},
proxy_protocol::expect::ExpectProxyProtocol,
Http, Pipe, SessionState,
Expand Down Expand Up @@ -230,8 +231,14 @@ impl HttpSession {
container_frontend_timeout.reset();
container_backend_timeout.reset();

let backend_buffer = if let ResponseStream::BackendAnswer(kawa) = http.response_stream {
kawa.storage.buffer
} else {
return None;
};

let mut pipe = Pipe::new(
http.response_stream.storage.buffer,
backend_buffer,
http.backend_id,
http.backend_socket,
http.backend,
Expand Down Expand Up @@ -580,14 +587,17 @@ impl HttpProxy {
})
}

pub fn add_cluster(&mut self, cluster: Cluster) -> Result<(), ProxyError> {
if let Some(answer_503) = &cluster.answer_503 {
pub fn add_cluster(&mut self, mut cluster: Cluster) -> Result<(), ProxyError> {
if let Some(answer_503) = cluster.answer_503.take() {
for listener in self.listeners.values() {
listener
.borrow()
.answers
.borrow_mut()
.add_custom_answer(&cluster.cluster_id, answer_503);
.add_custom_answer(&cluster.cluster_id, answer_503.clone())
.map_err(|(status, error)| {
ProxyError::AddCluster(ListenerError::TemplateParse(status, error))
})?;
}
}
self.clusters.insert(cluster.cluster_id.clone(), cluster);
Expand Down Expand Up @@ -717,10 +727,14 @@ impl HttpListener {
Ok(HttpListener {
active: false,
address: config.address.clone().into(),
answers: Rc::new(RefCell::new(HttpAnswers::new(
&config.answer_404,
&config.answer_503,
))),
answers: Rc::new(RefCell::new(
HttpAnswers::new(
// &config.answer_404,
// &config.answer_503,
RawAnswers::default(),
)
.map_err(|(status, error)| ListenerError::TemplateParse(status, error))?,
)),
config,
fronts: Router::new(),
listener: None,
Expand Down Expand Up @@ -1467,10 +1481,14 @@ mod tests {
listener: None,
address: address.into(),
fronts,
answers: Rc::new(RefCell::new(HttpAnswers::new(
"HTTP/1.1 404 Not Found\r\n\r\n",
"HTTP/1.1 503 Service Unavailable\r\n\r\n",
))),
answers: Rc::new(RefCell::new(
HttpAnswers::new(
// "HTTP/1.1 404 Not Found\r\n\r\n",
// "HTTP/1.1 503 Service Unavailable\r\n\r\n",
RawAnswers::default(),
)
.unwrap(),
)),
config: default_config,
token: Token(0),
active: true,
Expand Down
40 changes: 29 additions & 11 deletions lib/src/https.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ use crate::{
protocol::{
h2::Http2,
http::{
answers::HttpAnswers,
answers::{HttpAnswers, RawAnswers},
parser::{hostname_and_port, Method},
ResponseStream,
},
proxy_protocol::expect::ExpectProxyProtocol,
rustls::TlsHandshake,
Expand Down Expand Up @@ -357,8 +358,14 @@ impl HttpsSession {
container_frontend_timeout.reset();
container_backend_timeout.reset();

let backend_buffer = if let ResponseStream::BackendAnswer(kawa) = http.response_stream {
kawa.storage.buffer
} else {
return None;
};

let mut pipe = Pipe::new(
http.response_stream.storage.buffer,
backend_buffer,
http.backend_id,
http.backend_socket,
http.backend,
Expand Down Expand Up @@ -625,10 +632,14 @@ impl HttpsListener {
rustls_details: server_config,
active: false,
fronts: Router::new(),
answers: Rc::new(RefCell::new(HttpAnswers::new(
&config.answer_404,
&config.answer_503,
))),
answers: Rc::new(RefCell::new(
HttpAnswers::new(
// &config.answer_404,
// &config.answer_503,
RawAnswers::default(),
)
.map_err(|(status, error)| ListenerError::TemplateParse(status, error))?,
)),
config,
token,
tags: BTreeMap::new(),
Expand Down Expand Up @@ -1009,7 +1020,10 @@ impl HttpsProxy {
.borrow()
.answers
.borrow_mut()
.add_custom_answer(&cluster.cluster_id, &answer_503);
.add_custom_answer(&cluster.cluster_id, answer_503.clone())
.map_err(|(status, error)| {
ProxyError::AddCluster(ListenerError::TemplateParse(status, error))
})?;
}
}
self.clusters.insert(cluster.cluster_id.clone(), cluster);
Expand Down Expand Up @@ -1592,10 +1606,14 @@ mod tests {
fronts,
rustls_details,
resolver,
answers: Rc::new(RefCell::new(HttpAnswers::new(
"HTTP/1.1 404 Not Found\r\n\r\n",
"HTTP/1.1 503 Service Unavailable\r\n\r\n",
))),
answers: Rc::new(RefCell::new(
HttpAnswers::new(
// "HTTP/1.1 404 Not Found\r\n\r\n",
// "HTTP/1.1 503 Service Unavailable\r\n\r\n",
RawAnswers::default(),
)
.unwrap(),
)),
config: default_config,
token: Token(0),
active: true,
Expand Down
8 changes: 6 additions & 2 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ use std::{
use backends::BackendError;
use hex::FromHexError;
use mio::{net::TcpStream, Interest, Token};
use protocol::http::parser::Method;
use protocol::http::{answers::TemplateError, parser::Method};
use router::RouterError;
use time::{Duration, Instant};
use tls::CertificateResolverError;
Expand Down Expand Up @@ -642,6 +642,8 @@ pub enum ListenerError {
Resolver(CertificateResolverError),
#[error("failed to parse pem, {0}")]
PemParse(String),
#[error("failed to parse template {0}: {1}")]
TemplateParse(u16, TemplateError),
#[error("failed to build rustls context, {0}")]
BuildRustls(String),
#[error("could not activate listener with address {address:?}: {error}")]
Expand Down Expand Up @@ -671,8 +673,10 @@ pub enum ProxyError {
NoListenerFound(SocketAddr),
#[error("a listener is already present for this token")]
ListenerAlreadyPresent,
#[error("could not create add listener: {0}")]
#[error("could not add listener: {0}")]
AddListener(ListenerError),
#[error("could not add cluster: {0}")]
AddCluster(ListenerError),
#[error("failed to activate listener with address {address:?}: {listener_error}")]
ListenerActivation {
address: SocketAddr,
Expand Down
Loading

0 comments on commit b0fa2f0

Please sign in to comment.