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 authored and Keksoj committed Apr 5, 2024
1 parent f306a79 commit 3f0bfa1
Show file tree
Hide file tree
Showing 7 changed files with 842 additions and 322 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ hdrhistogram = "^7.5.4"
hex = "^0.4.3"
hpack = "^0.3.0"
idna = "^0.5.0"
kawa = { version = "^0.6.5", default-features = false }
kawa = { version = "^0.6.6", default-features = false }
libc = "^0.2.153"
memchr = "^2.7.1"
mio = { version = "^0.8.11", features = ["os-poll", "os-ext", "net"] }
Expand Down
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 @@ -586,14 +593,17 @@ impl HttpProxy {
Ok((owned.token, taken_listener))
}

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 @@ -715,10 +725,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 @@ -1458,10 +1472,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 @@ -54,8 +54,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 @@ -356,8 +357,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 @@ -624,10 +631,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 @@ -1003,7 +1014,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 @@ -1582,10 +1596,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
6 changes: 5 additions & 1 deletion lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,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 socket::ServerBindError;
use time::{Duration, Instant};
Expand Down Expand Up @@ -624,6 +624,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 @@ -655,6 +657,8 @@ pub enum ProxyError {
ListenerAlreadyPresent,
#[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 3f0bfa1

Please sign in to comment.