From 3f0bfa12a7fc83ba1487d85ea012e29a83af8aa5 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Wed, 21 Feb 2024 18:39:18 +0100 Subject: [PATCH] Dynamic automatic answers system - 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 --- Cargo.lock | 4 +- lib/Cargo.toml | 2 +- lib/src/http.rs | 44 +- lib/src/https.rs | 40 +- lib/src/lib.rs | 6 +- lib/src/protocol/kawa_h1/answers.rs | 610 ++++++++++++++++++++++++---- lib/src/protocol/kawa_h1/mod.rs | 458 +++++++++++---------- 7 files changed, 842 insertions(+), 322 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e827e8ab1..2d7904e84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -878,9 +878,9 @@ dependencies = [ [[package]] name = "kawa" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db85695c73b185d8f126377bcb663cede61f31c980cefe7bd0d711b2f949b9e0" +checksum = "f89584e325224583c25d100364472b83b57788f4fc1b823ce50f6804a4fdf5b9" dependencies = [ "nom", ] diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 71c321a27..429f87471 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -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"] } diff --git a/lib/src/http.rs b/lib/src/http.rs index 8df5b4fa4..912b16ab0 100644 --- a/lib/src/http.rs +++ b/lib/src/http.rs @@ -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, @@ -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, @@ -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); @@ -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, @@ -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, diff --git a/lib/src/https.rs b/lib/src/https.rs index e1dfe01d3..6ee4ec457 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -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, @@ -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, @@ -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(), @@ -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); @@ -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, diff --git a/lib/src/lib.rs b/lib/src/lib.rs index ae0d3635e..291b30d57 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -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}; @@ -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}")] @@ -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, diff --git a/lib/src/protocol/kawa_h1/answers.rs b/lib/src/protocol/kawa_h1/answers.rs index 556f6fd1c..f797f361f 100644 --- a/lib/src/protocol/kawa_h1/answers.rs +++ b/lib/src/protocol/kawa_h1/answers.rs @@ -1,32 +1,326 @@ -use std::{collections::HashMap, rc::Rc}; +use crate::{protocol::http::DefaultAnswer, sozu_command::state::ClusterId}; +use kawa::{ + h1::NoCallbacks, AsBuffer, Block, BodySize, Buffer, Chunk, Kawa, Kind, Pair, ParsingPhase, + StatusLine, Store, +}; +use std::{ + collections::{HashMap, VecDeque}, + fmt, + rc::Rc, +}; -use crate::{protocol::http::DefaultAnswerStatus, sozu_command::state::ClusterId}; +#[derive(Clone)] +pub struct SharedBuffer(Rc<[u8]>); + +impl AsBuffer for SharedBuffer { + fn as_buffer(&self) -> &[u8] { + &self.0 + } + + fn as_mut_buffer(&mut self) -> &mut [u8] { + panic!() + } +} + +pub type DefaultAnswerStream = Kawa; + +#[derive(thiserror::Error, Debug)] +pub enum TemplateError { + #[error("invalid template type: request was found, expected response")] + InvalidType, + #[error("template seems invalid or incomplete: {0:?}")] + InvalidTemplate(ParsingPhase), + #[error("unexpected status code: {0}")] + InvalidStatusCode(u16), + #[error("streaming is not supported in templates")] + UnsupportedStreaming, + #[error("template variable {0} is not allowed in headers")] + NotAllowedInHeader(&'static str), + #[error("template variable {0} is not allowed in body")] + NotAllowedInBody(&'static str), + #[error("template variable {0} can only be used once")] + AlreadyConsumed(&'static str), +} + +#[derive(Clone, Copy, Debug)] +struct TemplateVariable { + name: &'static str, + valid_in_body: bool, + valid_in_header: bool, + typ: ReplacementType, +} + +#[derive(Clone, Copy, Debug)] +enum ReplacementType { + Variable(usize), + VariableOnce(usize), + ContentLength, +} + +#[derive(Clone, Copy, Debug)] +struct Replacement { + block_index: usize, + typ: ReplacementType, +} + +pub struct Template { + kawa: DefaultAnswerStream, + body_replacements: Vec, + header_replacements: Vec, + // Size of body without any variables + body_size: usize, +} + +impl fmt::Debug for Template { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Template") + .field("body_replacements", &self.body_replacements) + .field("header_replacements", &self.header_replacements) + .field("body_size", &self.body_size) + .finish() + } +} + +impl Template { + fn new( + status: u16, + answer: Vec, + variables: &[TemplateVariable], + ) -> Result { + Self::_new(status, answer, variables).map_err(|e| (status, e)) + } + + fn _new( + status: u16, + answer: Vec, + variables: &[TemplateVariable], + ) -> Result { + let len = answer.len(); + let mut kawa = Kawa::new(Kind::Response, Buffer::new(SharedBuffer(Rc::from(answer)))); + kawa.storage.end = len; + kawa::h1::parse(&mut kawa, &mut NoCallbacks); + if !kawa.is_main_phase() { + return Err(TemplateError::InvalidTemplate(kawa.parsing_phase)); + } + if let StatusLine::Response { code, .. } = kawa.detached.status_line { + if code != status { + return Err(TemplateError::InvalidStatusCode(code)); + } + } else { + return Err(TemplateError::InvalidType); + } + let buf = kawa.storage.buffer(); + let mut blocks = VecDeque::new(); + let mut header_replacements = Vec::new(); + let mut body_replacements = Vec::new(); + let mut body_size = 0; + let mut used_once = Vec::new(); + for mut block in kawa.blocks.into_iter() { + match &mut block { + Block::ChunkHeader(_) => return Err(TemplateError::UnsupportedStreaming), + Block::StatusLine | Block::Cookies | Block::Flags(_) => { + blocks.push_back(block); + } + Block::Header(Pair { key, val }) => { + let val_data = val.data(buf); + let key_data = key.data(buf); + if let Some(b'%') = val_data.first() { + for variable in variables { + if &val_data[1..] == variable.name.as_bytes() { + if !variable.valid_in_header { + return Err(TemplateError::NotAllowedInHeader(variable.name)); + } + *val = Store::Static(b"PLACEHOLDER"); + match variable.typ { + ReplacementType::Variable(_) => {} + ReplacementType::VariableOnce(var_index) => { + if used_once.contains(&var_index) { + return Err(TemplateError::AlreadyConsumed( + variable.name, + )); + } + used_once.push(var_index); + } + ReplacementType::ContentLength => { + if let Some(b'%') = key_data.first() { + *key = Store::new_slice(buf, &key_data[1..]); + } + } + } + header_replacements.push(Replacement { + block_index: blocks.len(), + typ: variable.typ, + }); + break; + } + } + } + blocks.push_back(block); + } + Block::Chunk(Chunk { data }) => { + let data = data.data(buf); + body_size += data.len(); + let mut start = 0; + let mut i = 0; + while i < data.len() { + if data[i] == b'%' { + for variable in variables { + if data[i + 1..].starts_with(variable.name.as_bytes()) { + if !variable.valid_in_body { + return Err(TemplateError::NotAllowedInBody(variable.name)); + } + if start < i { + blocks.push_back(Block::Chunk(Chunk { + data: Store::new_slice(buf, &data[start..i]), + })); + } + body_size -= variable.name.len() + 1; + start = i + variable.name.len() + 1; + i += variable.name.len(); + match variable.typ { + ReplacementType::Variable(_) => {} + ReplacementType::ContentLength => {} + ReplacementType::VariableOnce(var_index) => { + if used_once.contains(&var_index) { + return Err(TemplateError::AlreadyConsumed( + variable.name, + )); + } + used_once.push(var_index); + } + } + body_replacements.push(Replacement { + block_index: blocks.len(), + typ: variable.typ, + }); + blocks.push_back(Block::Chunk(Chunk { + data: Store::Static(b"PLACEHOLDER"), + })); + break; + } + } + } + i += 1; + } + if start < data.len() { + blocks.push_back(Block::Chunk(Chunk { + data: Store::new_slice(buf, &data[start..]), + })); + } + } + } + } + kawa.blocks = blocks; + Ok(Self { + kawa, + body_replacements, + header_replacements, + body_size, + }) + } + + fn fill(&self, variables: &[&[u8]], variables_once: &mut [Vec]) -> DefaultAnswerStream { + let mut blocks = self.kawa.blocks.clone(); + let mut body_size = self.body_size; + for replacement in &self.body_replacements { + match replacement.typ { + ReplacementType::Variable(var_index) => { + let variable = &variables[var_index]; + body_size += variable.len(); + blocks[replacement.block_index] = Block::Chunk(Chunk { + data: Store::from_slice(variable), + }) + } + ReplacementType::VariableOnce(var_index) => { + let variable = std::mem::take(&mut variables_once[var_index]); + body_size += variable.len(); + blocks[replacement.block_index] = Block::Chunk(Chunk { + data: Store::Alloc(variable.into_boxed_slice(), 0), + }) + } + ReplacementType::ContentLength => unreachable!(), + } + } + for replacement in &self.header_replacements { + if let Block::Header(pair) = &mut blocks[replacement.block_index] { + match replacement.typ { + ReplacementType::Variable(var_index) => { + pair.val = Store::from_slice(variables[var_index]); + } + ReplacementType::VariableOnce(var_index) => { + let variable = std::mem::take(&mut variables_once[var_index]); + pair.val = Store::Alloc(variable.into_boxed_slice(), 0); + } + ReplacementType::ContentLength => { + pair.val = + Store::Alloc(body_size.to_string().into_bytes().into_boxed_slice(), 0) + } + } + } + } + Kawa { + storage: Buffer::new(self.kawa.storage.buffer.clone()), + blocks, + out: Default::default(), + detached: self.kawa.detached.clone(), + kind: Kind::Response, + expects: 0, + parsing_phase: ParsingPhase::Terminated, + body_size: BodySize::Length(body_size), + consumed: false, + } + } +} +pub struct RawAnswers { + /// MovedPermanently + pub answer_301: Vec, + /// BadRequest + pub answer_400: Vec, + /// Unauthorized + pub answer_401: Vec, + /// NotFound + pub answer_404: Vec, + /// RequestTimeout + pub answer_408: Vec, + /// PayloadTooLarge + pub answer_413: Vec, + /// BadGateway + pub answer_502: Vec, + /// ServiceUnavailable + pub answer_503: Vec, + /// GatewayTimeout + pub answer_504: Vec, + /// InsufficientStorage + pub answer_507: Vec, +} -#[allow(non_snake_case)] pub struct DefaultAnswers { - /// 400 - pub BadRequest: Rc>, - /// 401 - pub Unauthorized: Rc>, - /// 404 - pub NotFound: Rc>, - /// 408 - pub RequestTimeout: Rc>, - /// 413 - pub PayloadTooLarge: Rc>, - /// 502 - pub BadGateway: Rc>, - /// 503 - pub ServiceUnavailable: Rc>, - /// 504 - pub GatewayTimeout: Rc>, - /// 507 - pub InsufficientStorage: Rc>, + /// MovedPermanently + pub answer_301: Template, + /// BadRequest + pub answer_400: Template, + /// Unauthorized + pub answer_401: Template, + /// NotFound + pub answer_404: Template, + /// RequestTimeout + pub answer_408: Template, + /// PayloadTooLarge + pub answer_413: Template, + /// BadGateway + pub answer_502: Template, + /// ServiceUnavailable + pub answer_503: Template, + /// GatewayTimeout + pub answer_504: Template, + /// InsufficientStorage + pub answer_507: Template, } #[allow(non_snake_case)] pub struct CustomAnswers { - pub ServiceUnavailable: Option>>, + /// ServiceUnavailable + pub answer_503: Template, } pub struct HttpAnswers { @@ -34,67 +328,241 @@ pub struct HttpAnswers { pub custom: HashMap, } +impl Default for RawAnswers { + fn default() -> Self { + Self { + answer_301: Vec::from( + &b"\ +HTTP/1.1 301 Moved Permanently\r +Location: %REDIRECT_LOCATION\r +Connection: close\r +Content-Length: 0\r +Sozu-Id: %SOZU_ID\r +\r\n"[..], + ), + answer_400: Vec::from( + &b"\ +HTTP/1.1 400 Bad Request\r +Cache-Control: no-cache\r +Connection: close\r +%Content-Length: %CONTENT_LENGTH\r +Sozu-Id: %SOZU_ID\r +\r +

Sozu automatic 400 answer

+

Request %SOZU_ID could not be parsed.

+
+Kawa error: %DETAILS
+
+"[..],
+            ),
+            answer_401: Vec::from(
+                &b"\
+HTTP/1.1 401 Unauthorized\r
+Cache-Control: no-cache\r
+Connection: close\r
+Sozu-Id: %SOZU_ID\r
+\r\n"[..],
+            ),
+            answer_404: Vec::from(
+                &b"\
+HTTP/1.1 404 Not Found\r
+Cache-Control: no-cache\r
+Connection: close\r
+Sozu-Id: %SOZU_ID\r
+\r\n"[..],
+            ),
+            answer_408: Vec::from(
+                &b"\
+HTTP/1.1 408 Request Timeout\r
+Cache-Control: no-cache\r
+Connection: close\r
+Sozu-Id: %SOZU_ID\r
+\r\n"[..],
+            ),
+            answer_413: Vec::from(
+                &b"\
+HTTP/1.1 413 Payload Too Large\r
+Cache-Control: no-cache\r
+Connection: close\r
+%Content-Length: %CONTENT_LENGTH\r
+Sozu-Id: %SOZU_ID\r
+\r
+

Sozu automatic 413 answer

+

Request %SOZU_ID failed because all its headers could not be contained at once

+
+Kawa cursors: %DETAILS
+
+"[..],
+            ),
+            answer_502: Vec::from(
+                &b"\
+HTTP/1.1 502 Bad Gateway\r
+Cache-Control: no-cache\r
+Connection: close\r
+%Content-Length: %CONTENT_LENGTH\r
+Sozu-Id: %SOZU_ID\r
+\r
+

Sozu automatic 502 answer

+

Response to %SOZU_ID could not be parsed.

+
+Kawa error: %DETAILS
+
+"[..],
+            ),
+            answer_503: Vec::from(
+                &b"\
+HTTP/1.1 503 Service Unavailable\r
+Cache-Control: no-cache\r
+Connection: close\r
+%Content-Length: %CONTENT_LENGTH\r
+Sozu-Id: %SOZU_ID\r
+\r
+

Sozu automatic 503 answer

+

Could not find an available backend for request %SOZU_ID.

+
+%DETAILS
+
+"[..],
+            ),
+            answer_504: Vec::from(
+                &b"\
+HTTP/1.1 504 Gateway Timeout\r
+Cache-Control: no-cache\r
+Connection: close\r
+Sozu-Id: %SOZU_ID\r
+\r\n"[..],
+            ),
+            answer_507: Vec::from(
+                &b"\
+HTTP/1.1 507 Insufficient Storage\r
+Cache-Control: no-cache\r
+Connection: close\r
+%Content-Length: %CONTENT_LENGTH\r
+Sozu-Id: %SOZU_ID\r
+\r
+

Sozu automatic 507 answer

+

Response to %SOZU_ID failed because all its headers could not be contained at once.

+
+Kawa cursors: %DETAILS
+
+"[..],
+            ),
+        }
+    }
+}
+
 impl HttpAnswers {
-    pub fn new(answer_404: &str, answer_503: &str) -> Self {
-        HttpAnswers {
+    pub fn new(answers: RawAnswers) -> Result {
+        let sozu_id = TemplateVariable {
+            name: "SOZU_ID",
+            valid_in_body: true,
+            valid_in_header: true,
+            typ: ReplacementType::Variable(0),
+        };
+        let length = TemplateVariable {
+            name: "CONTENT_LENGTH",
+            valid_in_body: false,
+            valid_in_header: true,
+            typ: ReplacementType::ContentLength,
+        };
+        let details = TemplateVariable {
+            name: "DETAILS",
+            valid_in_body: true,
+            valid_in_header: false,
+            typ: ReplacementType::VariableOnce(0),
+        };
+        let location = TemplateVariable {
+            name: "REDIRECT_LOCATION",
+            valid_in_body: false,
+            valid_in_header: true,
+            typ: ReplacementType::VariableOnce(0),
+        };
+        Ok(HttpAnswers {
             default: DefaultAnswers {
-                BadRequest: Rc::new(Vec::from(
-                    &b"HTTP/1.1 400 Bad Request\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"[..]
-                )),
-                Unauthorized: Rc::new(Vec::from(
-                    &b"HTTP/1.1 401 Unauthorized\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"[..]
-                )),
-                NotFound: Rc::new(Vec::from(answer_404.as_bytes())),
-                RequestTimeout: Rc::new(Vec::from(
-                    &b"HTTP/1.1 408 Request Timeout\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"[..]
-                )),
-                PayloadTooLarge: Rc::new(Vec::from(
-                    &b"HTTP/1.1 413 Payload Too Large\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"[..]
-                )),
-                BadGateway: Rc::new(Vec::from(
-                    &b"HTTP/1.1 502 Bad Gateway\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"[..]
-                )),
-                ServiceUnavailable: Rc::new(Vec::from(answer_503.as_bytes())),
-                GatewayTimeout: Rc::new(Vec::from(
-                    &b"HTTP/1.1 504 Gateway Timeout\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"[..]
-                )),
-                InsufficientStorage: Rc::new(Vec::from(
-                    &b"HTTP/1.1 507 Insufficient Storage\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"[..]
-                )),
+                answer_301: Template::new(301, answers.answer_301, &[sozu_id, length, location])?,
+                answer_400: Template::new(400, answers.answer_400, &[sozu_id, length, details])?,
+                answer_401: Template::new(401, answers.answer_401, &[sozu_id, length])?,
+                answer_404: Template::new(404, answers.answer_404, &[sozu_id, length])?,
+                answer_408: Template::new(408, answers.answer_408, &[sozu_id, length])?,
+                answer_413: Template::new(413, answers.answer_413, &[sozu_id, length, details])?,
+                answer_502: Template::new(502, answers.answer_502, &[sozu_id, length, details])?,
+                answer_503: Template::new(503, answers.answer_503, &[sozu_id, length, details])?,
+                answer_504: Template::new(504, answers.answer_504, &[sozu_id, length])?,
+                answer_507: Template::new(507, answers.answer_507, &[sozu_id, length, details])?,
             },
             custom: HashMap::new(),
-        }
+        })
     }
 
-    pub fn add_custom_answer(&mut self, cluster_id: &str, answer_503: &str) {
-        let owned_answer_503 = answer_503.to_owned();
+    pub fn add_custom_answer(
+        &mut self,
+        cluster_id: &str,
+        answer_503: String,
+    ) -> Result<(), (u16, TemplateError)> {
+        let answer_503 = Template::new(503, answer_503.into(), &[])?;
         self.custom
-            .entry(cluster_id.to_string())
-            .and_modify(|c| c.ServiceUnavailable = Some(Rc::new(owned_answer_503.into_bytes())))
-            .or_insert(CustomAnswers {
-                ServiceUnavailable: Some(Rc::new(answer_503.to_owned().into_bytes())),
-            });
+            .insert(cluster_id.to_string(), CustomAnswers { answer_503 });
+        Ok(())
     }
 
     pub fn remove_custom_answer(&mut self, cluster_id: &str) {
         self.custom.remove(cluster_id);
     }
 
-    pub fn get(&self, answer: DefaultAnswerStatus, cluster_id: Option<&str>) -> Rc> {
-        match answer {
-            DefaultAnswerStatus::Answer301 => panic!("the 301 answer is generated dynamically"),
-            DefaultAnswerStatus::Answer400 => self.default.BadRequest.clone(),
-            DefaultAnswerStatus::Answer401 => self.default.Unauthorized.clone(),
-            DefaultAnswerStatus::Answer404 => self.default.NotFound.clone(),
-            DefaultAnswerStatus::Answer408 => self.default.RequestTimeout.clone(),
-            DefaultAnswerStatus::Answer413 => self.default.PayloadTooLarge.clone(),
-            DefaultAnswerStatus::Answer502 => self.default.BadGateway.clone(),
-            DefaultAnswerStatus::Answer503 => cluster_id
-                .and_then(|id: &str| self.custom.get(id))
-                .and_then(|c| c.ServiceUnavailable.clone())
-                .unwrap_or_else(|| self.default.ServiceUnavailable.clone()),
-            DefaultAnswerStatus::Answer504 => self.default.GatewayTimeout.clone(),
-            DefaultAnswerStatus::Answer507 => self.default.InsufficientStorage.clone(),
-        }
+    pub fn get(
+        &self,
+        answer: DefaultAnswer,
+        cluster_id: Option<&str>,
+        request_id: String,
+    ) -> DefaultAnswerStream {
+        let mut variables_once: Vec>;
+        let template = match answer {
+            DefaultAnswer::Answer301 { location } => {
+                variables_once = vec![location.into()];
+                &self.default.answer_301
+            }
+            DefaultAnswer::Answer400 { details } => {
+                variables_once = vec![details.into()];
+                &self.default.answer_400
+            }
+            DefaultAnswer::Answer401 {} => {
+                variables_once = vec![];
+                &self.default.answer_401
+            }
+            DefaultAnswer::Answer404 {} => {
+                variables_once = vec![];
+                &self.default.answer_404
+            }
+            DefaultAnswer::Answer408 {} => {
+                variables_once = vec![];
+                &self.default.answer_408
+            }
+            DefaultAnswer::Answer413 { details } => {
+                variables_once = vec![details.into()];
+                &self.default.answer_413
+            }
+            DefaultAnswer::Answer502 { details } => {
+                variables_once = vec![details.into()];
+                &self.default.answer_502
+            }
+            DefaultAnswer::Answer503 { details } => {
+                variables_once = vec![details.into()];
+                cluster_id
+                    .and_then(|id: &str| self.custom.get(id))
+                    .map(|c| &c.answer_503)
+                    .unwrap_or_else(|| &self.default.answer_503)
+            }
+            DefaultAnswer::Answer504 {} => {
+                variables_once = vec![];
+                &self.default.answer_504
+            }
+            DefaultAnswer::Answer507 { details } => {
+                variables_once = vec![details.into()];
+                &self.default.answer_507
+            }
+        };
+        // kawa::debug_kawa(&template.kawa);
+        // println!("{template:#?}");
+        template.fill(&[request_id.as_bytes()], &mut variables_once)
     }
 }
diff --git a/lib/src/protocol/kawa_h1/mod.rs b/lib/src/protocol/kawa_h1/mod.rs
index bc207659c..a5219a568 100644
--- a/lib/src/protocol/kawa_h1/mod.rs
+++ b/lib/src/protocol/kawa_h1/mod.rs
@@ -22,7 +22,7 @@ use crate::{
     backends::{Backend, BackendError},
     pool::{Checkout, Pool},
     protocol::{
-        http::{editor::HttpContext, parser::Method},
+        http::{answers::DefaultAnswerStream, editor::HttpContext, parser::Method},
         SessionState,
     },
     retry::RetryPolicy,
@@ -51,40 +51,32 @@ impl kawa::AsBuffer for Checkout {
 }
 
 #[derive(Debug, Clone, PartialEq, Eq)]
-pub enum SessionStatus {
-    Normal,
-    /// status, HTTP answer, index in HTTP answer
-    DefaultAnswer(DefaultAnswerStatus, Rc>, usize),
+pub enum DefaultAnswer {
+    Answer301 { location: String },
+    Answer400 { details: String },
+    Answer401 {},
+    Answer404 {},
+    Answer408 {},
+    Answer413 { details: String },
+    Answer502 { details: String },
+    Answer503 { details: String },
+    Answer504 {},
+    Answer507 { details: String },
 }
 
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum DefaultAnswerStatus {
-    Answer301,
-    Answer400,
-    Answer401,
-    Answer404,
-    Answer408,
-    Answer413,
-    Answer502,
-    Answer503,
-    Answer504,
-    Answer507,
-}
-
-#[allow(clippy::from_over_into)]
-impl Into for DefaultAnswerStatus {
-    fn into(self) -> u16 {
-        match self {
-            Self::Answer301 => 301,
-            Self::Answer400 => 400,
-            Self::Answer401 => 401,
-            Self::Answer404 => 404,
-            Self::Answer408 => 408,
-            Self::Answer413 => 413,
-            Self::Answer502 => 502,
-            Self::Answer503 => 503,
-            Self::Answer504 => 504,
-            Self::Answer507 => 507,
+impl From<&DefaultAnswer> for u16 {
+    fn from(answer: &DefaultAnswer) -> u16 {
+        match answer {
+            DefaultAnswer::Answer301 { .. } => 301,
+            DefaultAnswer::Answer400 { .. } => 400,
+            DefaultAnswer::Answer401 { .. } => 401,
+            DefaultAnswer::Answer404 { .. } => 404,
+            DefaultAnswer::Answer408 { .. } => 408,
+            DefaultAnswer::Answer413 { .. } => 413,
+            DefaultAnswer::Answer502 { .. } => 502,
+            DefaultAnswer::Answer503 { .. } => 503,
+            DefaultAnswer::Answer504 { .. } => 504,
+            DefaultAnswer::Answer507 { .. } => 507,
         }
     }
 }
@@ -97,6 +89,11 @@ pub enum TimeoutStatus {
     WaitingForResponse,
 }
 
+pub enum ResponseStream {
+    BackendAnswer(GenericHttpStream),
+    DefaultAnswer(u16, DefaultAnswerStream),
+}
+
 /// Http will be contained in State which itself is contained by Session
 pub struct Http {
     answers: Rc>,
@@ -121,8 +118,7 @@ pub struct Http {
     keepalive_count: usize,
     listener: Rc>,
     pub request_stream: GenericHttpStream,
-    pub response_stream: GenericHttpStream,
-    status: SessionStatus,
+    pub response_stream: ResponseStream,
     /// The HTTP context was separated from the State for borrowing reasons.
     /// Calling a kawa parser mutably borrows the State through request_stream or response_stream,
     /// so Http can't be borrowed again to be used in callbacks. HttContext is an independant
@@ -192,11 +188,10 @@ impl Http Http response_stream,
+            _ => return,
+        };
+
         self.context.id = Ulid::generate();
         self.context.reset();
 
         self.request_stream.clear();
-        self.response_stream.clear();
+        response_stream.clear();
         self.keepalive_count += 1;
         gauge_add!("http.active_requests", -1);
 
@@ -249,22 +249,20 @@ impl Http Http response_stream,
+            ResponseStream::DefaultAnswer(..) => {
+                error!(
+                    "{}\tsending default answer, should not read from front socket",
+                    self.log_context()
+                );
+                self.frontend_readiness.interest.remove(Ready::READABLE);
+                self.frontend_readiness.interest.insert(Ready::WRITABLE);
+                return StateResult::Continue;
+            }
+        };
 
         if self.request_stream.storage.is_full() {
             self.frontend_readiness.interest.remove(Ready::READABLE);
@@ -298,7 +299,10 @@ impl Http Http{:?}]: read {} bytes",
-            self.log_context(),
-            self.frontend_token.0,
-            self.backend_token,
-            size
+            "self.log_context()", self.frontend_token.0, self.backend_token, size
         );
 
         if size > 0 {
@@ -354,10 +355,6 @@ impl Http {}
         };
 
-        self.readable_parse(metrics)
-    }
-
-    pub fn readable_parse(&mut self, metrics: &mut SessionMetrics) -> StateResult {
         trace!("==============readable_parse");
         let was_initial = self.request_stream.is_initial();
         let was_not_proxying = !self.request_stream.is_main_phase();
@@ -379,7 +376,7 @@ impl Http {
@@ -398,11 +395,13 @@ impl Http message.to_owned(),
                 }
             );
-            if self.response_stream.consumed {
+            if response_stream.consumed {
                 self.log_request_error(metrics, "Parsing error on the request");
                 return StateResult::CloseSession;
             } else {
-                self.set_answer(DefaultAnswerStatus::Answer400, None);
+                self.set_answer(DefaultAnswer::Answer400 {
+                    details: format!("{marker:?}: {kind:?}"),
+                });
                 return StateResult::Continue;
             }
         }
@@ -425,14 +424,14 @@ impl Http StateResult {
         trace!("==============writable");
-        //handle default answers
-        if let SessionStatus::DefaultAnswer(_, _, _) = self.status {
-            return self.writable_default_answer(metrics);
-        }
+        let response_stream = match &mut self.response_stream {
+            ResponseStream::BackendAnswer(response_stream) => response_stream,
+            _ => return self.writable_default_answer(metrics),
+        };
 
-        self.response_stream.prepare(&mut kawa::h1::BlockConverter);
+        response_stream.prepare(&mut kawa::h1::BlockConverter);
 
-        let bufs = self.response_stream.as_io_slice();
+        let bufs = response_stream.as_io_slice();
         if bufs.is_empty() && !self.frontend_socket.socket_wants_write() {
             self.frontend_readiness.interest.remove(Ready::WRITABLE);
             return StateResult::Continue;
@@ -441,14 +440,11 @@ impl Http 0 {
-            self.response_stream.consume(size);
+            response_stream.consume(size);
             count!("bytes_out", size as i64);
             metrics.bout += size;
             self.backend_readiness.interest.insert(Ready::READABLE);
@@ -477,8 +473,7 @@ impl Http Http {
                     trace!("============== HANDLE UPGRADE!");
+                    self.log_request_success(metrics);
                     return StateResult::Upgrade;
                 }
                 kawa::StatusLine::Response { code: 100, .. } => {
                     trace!("============== HANDLE CONTINUE!");
-                    self.response_stream.clear();
+                    response_stream.clear();
+                    self.log_request_success(metrics);
                     return StateResult::Continue;
                 }
                 kawa::StatusLine::Response { code: 103, .. } => {
                     self.backend_readiness.event.insert(Ready::READABLE);
                     trace!("============== HANDLE EARLY HINT!");
-                    self.response_stream.clear();
+                    response_stream.clear();
+                    self.log_request_success(metrics);
                     return StateResult::Continue;
                 }
                 _ => (),
             }
 
-            let response_length_known = self.response_stream.body_size != kawa::BodySize::Empty;
+            let response_length_known = response_stream.body_size != kawa::BodySize::Empty;
             let request_length_known = self.request_stream.body_size != kawa::BodySize::Empty;
             if !(self.request_stream.is_terminated() && self.request_stream.is_completed())
                 && request_length_known
@@ -526,6 +524,7 @@ impl Http Http StateResult {
+        trace!("==============writable_default_answer");
+        let response_stream = match &mut self.response_stream {
+            ResponseStream::DefaultAnswer(_, response_stream) => response_stream,
+            _ => return StateResult::CloseSession,
+        };
+        let bufs = response_stream.as_io_slice();
+        let (size, socket_state) = self.frontend_socket.socket_write_vectored(&bufs);
+
+        count!("bytes_out", size as i64);
+        metrics.bout += size;
+        response_stream.consume(size);
+
+        if size == 0 || socket_state != SocketResult::Continue {
+            self.frontend_readiness.event.remove(Ready::WRITABLE);
+        }
+
+        if response_stream.is_completed() {
+            save_http_status_metric(self.context.status, self.log_context());
+            self.log_default_answer_success(metrics);
+            self.frontend_readiness.reset();
+            self.backend_readiness.reset();
+            return StateResult::CloseSession;
+        }
+
+        if socket_state == SocketResult::Error {
+            self.frontend_socket.write_error();
+            self.log_request_error(
+                metrics,
+                "error writing default answer to front socket, closing",
+            );
+            StateResult::CloseSession
+        } else {
+            StateResult::Continue
+        }
+    }
+
     pub fn backend_writable(&mut self, metrics: &mut SessionMetrics) -> SessionResult {
         trace!("==============backend_writable");
-        if let SessionStatus::DefaultAnswer(_, _, _) = self.status {
+        if let ResponseStream::DefaultAnswer(..) = self.response_stream {
             error!(
                 "{}\tsending default answer, should not write to back",
                 self.log_context()
@@ -638,15 +674,18 @@ impl Http response_stream,
+            _ => {
+                error!(
+                    "{}\tsending default answer, should not read from back socket",
+                    self.log_context()
+                );
+                self.backend_readiness.interest.remove(Ready::READABLE);
+                self.frontend_readiness.interest.insert(Ready::WRITABLE);
+                return SessionResult::Continue;
+            }
+        };
 
         let backend_socket = if let Some(backend_socket) = &mut self.backend_socket {
             backend_socket
@@ -655,18 +694,22 @@ impl Http Http 0 {
-            self.response_stream.storage.fill(size);
+            response_stream.storage.fill(size);
             count!("back_bytes_in", size as i64);
             metrics.backend_bin += size;
             // if self.kawa_response.storage.is_full() {
@@ -714,49 +757,46 @@ impl Http {}
         }
 
-        self.backend_readable_parse(metrics)
-    }
-
-    pub fn backend_readable_parse(&mut self, metrics: &mut SessionMetrics) -> SessionResult {
         trace!("==============backend_readable_parse");
-        kawa::h1::parse(&mut self.response_stream, &mut self.context);
+        kawa::h1::parse(response_stream, &mut self.context);
         // kawa::debug_kawa(&self.response_stream);
 
-        if let kawa::ParsingPhase::Error { marker, kind } = self.response_stream.parsing_phase {
+        if let kawa::ParsingPhase::Error { marker, kind } = response_stream.parsing_phase {
             incr!("http.backend_parse_errors");
             warn!(
                 "{} Parsing response error in {:?}: {}",
-                self.log_context(),
+                "self.log_context()",
                 marker,
                 match kind {
                     kawa::ParsingErrorKind::Consuming { index } => {
-                        let kawa = &self.response_stream;
                         parser::view(
-                            kawa.storage.used(),
+                            response_stream.storage.used(),
                             16,
                             &[
-                                kawa.storage.start,
-                                kawa.storage.head,
+                                response_stream.storage.start,
+                                response_stream.storage.head,
                                 index as usize,
-                                kawa.storage.end,
+                                response_stream.storage.end,
                             ],
                         )
                     }
                     kawa::ParsingErrorKind::Processing { message } => message.to_owned(),
                 }
             );
-            if self.response_stream.consumed {
+            if response_stream.consumed {
                 return SessionResult::Close;
             } else {
-                self.set_answer(DefaultAnswerStatus::Answer502, None);
+                self.set_answer(DefaultAnswer::Answer502 {
+                    details: format!("{marker:?}: {kind:?}"),
+                });
                 return SessionResult::Continue;
             }
         }
 
-        if self.response_stream.is_main_phase() {
+        if response_stream.is_main_phase() {
             self.frontend_readiness.interest.insert(Ready::WRITABLE);
         }
-        if self.response_stream.is_terminated() {
+        if response_stream.is_terminated() {
             metrics.backend_stop();
             self.backend_stop = Some(Instant::now());
             self.backend_readiness.interest.remove(Ready::READABLE);
@@ -775,16 +815,12 @@ impl Http EndpointRecord {
-        let status = match self.status {
-            SessionStatus::Normal => self.context.status,
-            SessionStatus::DefaultAnswer(answers, ..) => Some(answers.into()),
-        };
         EndpointRecord::Http {
             method: self.context.method.as_deref(),
             authority: self.context.authority.as_deref(),
             path: self.context.path.as_deref(),
             reason: self.context.reason.as_deref(),
-            status,
+            status: self.context.status,
         }
     }
 
@@ -865,7 +901,7 @@ impl Http Http>>) {
-        if let SessionStatus::DefaultAnswer(status, _, _) = self.status {
+    pub fn set_answer(&mut self, answer: DefaultAnswer) {
+        let status = u16::from(&answer);
+        if let ResponseStream::DefaultAnswer(old_status, ..) = self.response_stream {
             error!(
-                "already set the default answer to {:?}, trying to set to {:?}",
-                status, answer
+                "already set the default answer to {}, trying to set to {}",
+                old_status, status
             );
         } else {
             match answer {
-                DefaultAnswerStatus::Answer301 => incr!(
+                DefaultAnswer::Answer301 { .. } => incr!(
                     "http.301.redirection",
                     self.cluster_id.as_deref(),
                     self.backend_id.as_deref()
                 ),
-                DefaultAnswerStatus::Answer400 => incr!("http.400.errors"),
-                DefaultAnswerStatus::Answer401 => incr!(
+                DefaultAnswer::Answer400 { .. } => incr!("http.400.errors"),
+                DefaultAnswer::Answer401 { .. } => incr!(
                     "http.401.errors",
                     self.cluster_id.as_deref(),
                     self.backend_id.as_deref()
                 ),
-                DefaultAnswerStatus::Answer404 => incr!("http.404.errors"),
-                DefaultAnswerStatus::Answer408 => incr!(
+                DefaultAnswer::Answer404 { .. } => incr!("http.404.errors"),
+                DefaultAnswer::Answer408 { .. } => incr!(
                     "http.408.errors",
                     self.cluster_id.as_deref(),
                     self.backend_id.as_deref()
                 ),
-                DefaultAnswerStatus::Answer413 => incr!(
+                DefaultAnswer::Answer413 { .. } => incr!(
                     "http.413.errors",
                     self.cluster_id.as_deref(),
                     self.backend_id.as_deref()
                 ),
-                DefaultAnswerStatus::Answer502 => incr!(
+                DefaultAnswer::Answer502 { .. } => incr!(
                     "http.502.errors",
                     self.cluster_id.as_deref(),
                     self.backend_id.as_deref()
                 ),
-                DefaultAnswerStatus::Answer503 => incr!(
+                DefaultAnswer::Answer503 { .. } => incr!(
                     "http.503.errors",
                     self.cluster_id.as_deref(),
                     self.backend_id.as_deref()
                 ),
-                DefaultAnswerStatus::Answer504 => incr!(
+                DefaultAnswer::Answer504 { .. } => incr!(
                     "http.504.errors",
                     self.cluster_id.as_deref(),
                     self.backend_id.as_deref()
                 ),
-                DefaultAnswerStatus::Answer507 => incr!(
+                DefaultAnswer::Answer507 { .. } => incr!(
                     "http.507.errors",
                     self.cluster_id.as_deref(),
                     self.backend_id.as_deref()
@@ -937,64 +974,20 @@ impl Http StateResult {
-        trace!("==============writable_default_answer");
-        let socket_result = match self.status {
-            SessionStatus::DefaultAnswer(status, ref buf, mut index) => {
-                let len = buf.len();
-
-                let mut sz = 0usize;
-                let mut socket_result = SocketResult::Continue;
-                while socket_result == SocketResult::Continue && index < len {
-                    let (current_sz, current_res) =
-                        self.frontend_socket.socket_write(&buf[index..]);
-                    socket_result = current_res;
-                    sz += current_sz;
-                    index += current_sz;
-                }
-
-                count!("bytes_out", sz as i64);
-                metrics.bout += sz;
-
-                if socket_result != SocketResult::Continue {
-                    self.frontend_readiness.event.remove(Ready::WRITABLE);
-                }
-
-                if index == len {
-                    save_http_status_metric(Some(status.into()), self.log_context());
-                    self.log_default_answer_success(metrics);
-                    self.frontend_readiness.reset();
-                    self.backend_readiness.reset();
-                    return StateResult::CloseSession;
-                }
-
-                socket_result
-            }
-            _ => return StateResult::CloseSession,
-        };
-
-        if socket_result == SocketResult::Error {
-            self.frontend_socket.write_error();
-            self.log_request_error(
-                metrics,
-                "error writing default answer to front socket, closing",
-            );
-            StateResult::CloseSession
-        } else {
-            StateResult::Continue
-        }
-    }
-
     pub fn test_backend_socket(&self) -> bool {
         match self.backend_socket {
             Some(ref s) => {
@@ -1115,7 +1108,12 @@ impl Http Result<(), BackendConnectionError> {
         if self.connection_attempts >= CONN_RETRIES {
             error!("{} max connection attempt reached", self.log_context());
-            self.set_answer(DefaultAnswerStatus::Answer503, None);
+            self.set_answer(DefaultAnswer::Answer503 {
+                details: format!(
+                    "Max connection attempt reached: {}",
+                    self.connection_attempts
+                ),
+            });
             return Err(BackendConnectionError::MaxConnectionRetries(None));
         }
         Ok(())
@@ -1166,7 +1164,9 @@ impl Http tuple,
             Err(cluster_error) => {
-                self.set_answer(DefaultAnswerStatus::Answer400, None);
+                self.set_answer(DefaultAnswer::Answer400 {
+                    details: "...".into(),
+                });
                 return Err(cluster_error);
             }
         };
@@ -1179,7 +1179,7 @@ impl Http route,
             Err(frontend_error) => {
-                self.set_answer(DefaultAnswerStatus::Answer404, None);
+                self.set_answer(DefaultAnswer::Answer404 {});
                 return Err(RetrieveClusterError::RetrieveFrontend(frontend_error));
             }
         };
@@ -1187,7 +1187,7 @@ impl Http cluster_id,
             Route::Deny => {
-                self.set_answer(DefaultAnswerStatus::Answer401, None);
+                self.set_answer(DefaultAnswer::Answer401 {});
                 return Err(RetrieveClusterError::UnauthorizedRoute);
             }
         };
@@ -1201,11 +1201,9 @@ impl Http Http Http StateResult {
+        let response_stream = match &mut self.response_stream {
+            ResponseStream::BackendAnswer(response_stream) => response_stream,
+            _ => return StateResult::CloseBackend,
+        };
+
         // there might still data we can read on the socket
         if self.backend_readiness.event.is_readable()
             && self.backend_readiness.interest.is_readable()
@@ -1488,12 +1493,12 @@ impl Http Http Http Http TimeoutStatus {
         if self.request_stream.is_main_phase() {
-            if self.response_stream.is_initial() {
-                TimeoutStatus::WaitingForResponse
-            } else {
-                TimeoutStatus::Response
+            match &self.response_stream {
+                ResponseStream::BackendAnswer(kawa) if kawa.is_initial() => {
+                    TimeoutStatus::WaitingForResponse
+                }
+                _ => TimeoutStatus::Response,
             }
         } else if self.keepalive_count > 0 {
             TimeoutStatus::WaitingForNewRequest
@@ -1729,16 +1737,20 @@ impl SessionState
     ) -> SessionResult {
         let session_result = self.ready_inner(session, proxy, metrics);
         if session_result == SessionResult::Upgrade {
+            let response_storage = match &mut self.response_stream {
+                ResponseStream::BackendAnswer(response_stream) => &mut response_stream.storage,
+                _ => return SessionResult::Close,
+            };
+
             // sync the underlying Checkout buffers, if they contain remaining data
             // it will be processed once upgraded to websocket
             self.request_stream.storage.buffer.sync(
                 self.request_stream.storage.end,
                 self.request_stream.storage.head,
             );
-            self.response_stream.storage.buffer.sync(
-                self.response_stream.storage.end,
-                self.response_stream.storage.head,
-            );
+            response_storage
+                .buffer
+                .sync(response_storage.end, response_storage.head);
         }
         session_result
     }
@@ -1772,12 +1784,14 @@ impl SessionState
             return match self.timeout_status() {
                 // we do not have a complete answer
                 TimeoutStatus::Request => {
-                    self.set_answer(DefaultAnswerStatus::Answer408, None);
+                    self.set_answer(DefaultAnswer::Answer408 {});
                     self.writable(metrics)
                 }
                 // we have a complete answer but the response did not start
                 TimeoutStatus::WaitingForResponse => {
-                    self.set_answer(DefaultAnswerStatus::Answer408, None);
+                    // this case is ambiguous, as it is the frontend timeout that triggers while we were waiting for response
+                    // the timeout responsibility should have switched before
+                    self.set_answer(DefaultAnswer::Answer504 {});
                     self.writable(metrics)
                 }
                 // we have a complete answer and the start of a response, but the request was not tagged as terminated
@@ -1796,11 +1810,11 @@ impl SessionState
                     error!(
                         "got backend timeout while waiting for a request, this should not happen"
                     );
-                    self.set_answer(DefaultAnswerStatus::Answer504, None);
+                    self.set_answer(DefaultAnswer::Answer504 {});
                     self.writable(metrics)
                 }
                 TimeoutStatus::WaitingForResponse => {
-                    self.set_answer(DefaultAnswerStatus::Answer504, None);
+                    self.set_answer(DefaultAnswer::Answer504 {});
                     self.writable(metrics)
                 }
                 TimeoutStatus::Response => {
@@ -1831,21 +1845,19 @@ impl SessionState
 \tFrontend:
 \t\ttoken: {:?}\treadiness: {:?}\tstate: {:?}
 \tBackend:
-\t\ttoken: {:?}\treadiness: {:?}\tstate: {:?}",
+\t\ttoken: {:?}\treadiness: {:?}\tstate: {{:?}}",
             context,
             self.frontend_token,
             self.frontend_readiness,
             self.request_stream.parsing_phase,
             self.backend_token,
-            self.backend_readiness,
-            self.response_stream.parsing_phase
+            self.backend_readiness // self.response_stream.parsing_phase
         );
     }
 
     fn shutting_down(&mut self) -> SessionIsToBeClosed {
-        if self.request_stream.is_initial()
-            && self.request_stream.storage.is_empty()
-            && self.response_stream.storage.is_empty()
+        if self.request_stream.is_initial() && self.request_stream.storage.is_empty()
+        // && self.response_stream.storage.is_empty()
         {
             true
         } else {