diff --git a/command/assets/custom_200.html b/command/assets/custom_200.html
index 73e5926d9..12f2ca492 100644
--- a/command/assets/custom_200.html
+++ b/command/assets/custom_200.html
@@ -1,5 +1,4 @@
HTTP/1.1 200 OK
-%Content-Length: %CONTENT_LENGTH
Sozu-Id: %REQUEST_ID
%CLUSTER_ID Custom 200
diff --git a/command/assets/custom_404.html b/command/assets/custom_404.html
index 8302d4319..e331b285e 100644
--- a/command/assets/custom_404.html
+++ b/command/assets/custom_404.html
@@ -1,6 +1,5 @@
HTTP/1.1 404 Not Found
Cache-Control: no-cache
-Connection: close
Sozu-Id: %REQUEST_ID
My own 404 error page
diff --git a/command/assets/custom_503.html b/command/assets/custom_503.html
index ac12b8825..4484f8a4e 100644
--- a/command/assets/custom_503.html
+++ b/command/assets/custom_503.html
@@ -1,7 +1,6 @@
HTTP/1.1 503 Service Unavailable
Cache-Control: no-cache
Connection: close
-%Content-Length: %CONTENT_LENGTH
Sozu-Id: %REQUEST_ID
MyCluster: 503 Service Unavailable
diff --git a/command/src/state.rs b/command/src/state.rs
index 9413fa203..0d6524b23 100644
--- a/command/src/state.rs
+++ b/command/src/state.rs
@@ -1724,6 +1724,7 @@ mod tests {
hostname: String::from("test.local"),
path: PathRule::prefix(String::from("/abc")),
address: SocketAddress::new_v4(0, 0, 0, 0, 8080),
+ required_auth: Some(false),
redirect: Some(RedirectPolicy::Forward.into()),
redirect_scheme: Some(RedirectScheme::UseSame.into()),
..Default::default()
diff --git a/e2e/src/http_utils/mod.rs b/e2e/src/http_utils/mod.rs
index 9e6248df2..345ebc1a3 100644
--- a/e2e/src/http_utils/mod.rs
+++ b/e2e/src/http_utils/mod.rs
@@ -24,12 +24,17 @@ pub fn http_request, S2: Into, S3: Into, S4: In
)
}
-pub fn immutable_answer(status: u16) -> String {
+pub fn immutable_answer(status: u16, content_length: bool) -> String {
+ let content_length = if content_length {
+ "\r\nContent-Length: 0"
+ } else {
+ ""
+ };
match status {
- 400 => String::from("HTTP/1.1 400 Bad Request\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"),
- 404 => String::from("HTTP/1.1 404 Not Found\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"),
- 502 => String::from("HTTP/1.1 502 Bad Gateway\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"),
- 503 => String::from("HTTP/1.1 503 Service Unavailable\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"),
+ 400 => format!("HTTP/1.1 400 Bad Request\r\nCache-Control: no-cache\r\nConnection: close{content_length}\r\n\r\n"),
+ 404 => format!("HTTP/1.1 404 Not Found\r\nCache-Control: no-cache\r\nConnection: close{content_length}\r\n\r\n"),
+ 502 => format!("HTTP/1.1 502 Bad Gateway\r\nCache-Control: no-cache\r\nConnection: close{content_length}\r\n\r\n"),
+ 503 => format!("HTTP/1.1 503 Service Unavailable\r\nCache-Control: no-cache\r\nConnection: close{content_length}\r\n\r\n"),
_ => unimplemented!()
}
}
diff --git a/e2e/src/tests/tests.rs b/e2e/src/tests/tests.rs
index 89727f53e..4fb54f908 100644
--- a/e2e/src/tests/tests.rs
+++ b/e2e/src/tests/tests.rs
@@ -645,10 +645,10 @@ fn try_http_behaviors() -> State {
.to_http(None)
.unwrap();
http_config.answers = BTreeMap::from([
- ("400".to_string(), immutable_answer(400)),
- ("404".to_string(), immutable_answer(404)),
- ("502".to_string(), immutable_answer(502)),
- ("503".to_string(), immutable_answer(503)),
+ ("400".to_string(), immutable_answer(400, false)),
+ ("404".to_string(), immutable_answer(404, false)),
+ ("502".to_string(), immutable_answer(502, false)),
+ ("503".to_string(), immutable_answer(503, false)),
]);
worker.send_proxy_request_type(RequestType::AddHttpListener(http_config));
@@ -671,7 +671,7 @@ fn try_http_behaviors() -> State {
let response = client.receive();
println!("response: {response:?}");
- assert_eq!(response, Some(immutable_answer(404)));
+ assert_eq!(response, Some(immutable_answer(404, true)));
assert_eq!(client.receive(), None);
worker.send_proxy_request_type(RequestType::AddHttpFrontend(RequestHttpFrontend {
@@ -686,7 +686,7 @@ fn try_http_behaviors() -> State {
let response = client.receive();
println!("response: {response:?}");
- assert_eq!(response, Some(immutable_answer(503)));
+ assert_eq!(response, Some(immutable_answer(503, true)));
assert_eq!(client.receive(), None);
let back_address = create_local_address();
@@ -706,7 +706,7 @@ fn try_http_behaviors() -> State {
let response = client.receive();
println!("response: {response:?}");
- assert_eq!(response, Some(immutable_answer(400)));
+ assert_eq!(response, Some(immutable_answer(400, true)));
assert_eq!(client.receive(), None);
let mut backend = SyncBackend::new("backend", back_address, "TEST\r\n\r\n");
@@ -723,7 +723,7 @@ fn try_http_behaviors() -> State {
let response = client.receive();
println!("request: {request:?}");
println!("response: {response:?}");
- assert_eq!(response, Some(immutable_answer(502)));
+ assert_eq!(response, Some(immutable_answer(502, true)));
assert_eq!(client.receive(), None);
info!("expecting 200");
@@ -786,7 +786,7 @@ fn try_http_behaviors() -> State {
let response = client.receive();
println!("request: {request:?}");
println!("response: {response:?}");
- assert_eq!(response, Some(immutable_answer(503)));
+ assert_eq!(response, Some(immutable_answer(503, true)));
assert_eq!(client.receive(), None);
worker.send_proxy_request_type(RequestType::RemoveBackend(RemoveBackend {
@@ -984,7 +984,7 @@ fn try_https_redirect() -> State {
client.connect();
client.send();
let answer = client.receive();
- let expected_answer = format!("{answer_301_prefix}https://example.com/redirected?true\r\n\r\n");
+ let expected_answer = format!("{answer_301_prefix}https://example.com/redirected?true\r\nContent-Length: 0\r\n\r\n");
assert_eq!(answer, Some(expected_answer));
State::Success
diff --git a/lib/src/http.rs b/lib/src/http.rs
index a430c1f17..8c2b20c07 100644
--- a/lib/src/http.rs
+++ b/lib/src/http.rs
@@ -209,11 +209,14 @@ impl HttpSession {
}
}
- fn upgrade_http(&mut self, http: Http) -> Option {
+ fn upgrade_http(
+ &mut self,
+ mut http: Http,
+ ) -> Option {
debug!("http switching to ws");
- let front_token = self.frontend_token;
- let back_token = match http.backend_token {
- Some(back_token) => back_token,
+ let frontend_token = self.frontend_token;
+ let origin = match http.origin.take() {
+ Some(origin) => origin,
None => {
warn!(
"Could not upgrade http request on cluster '{:?}' ({:?}) using backend '{:?}' into websocket for request '{}'",
@@ -223,7 +226,7 @@ impl HttpSession {
}
};
- let ws_context = http.websocket_context();
+ let websocket_context = http.websocket_context();
let mut container_frontend_timeout = http.container_frontend_timeout;
let mut container_backend_timeout = http.container_backend_timeout;
container_frontend_timeout.reset();
@@ -237,25 +240,25 @@ impl HttpSession {
let mut pipe = Pipe::new(
backend_buffer,
- http.context.backend_id,
- http.backend_socket,
- http.backend,
+ Some(origin.backend_id),
+ Some(origin.socket),
+ Some(origin.backend),
Some(container_backend_timeout),
Some(container_frontend_timeout),
http.context.cluster_id,
http.request_stream.storage.buffer,
- front_token,
+ frontend_token,
http.frontend_socket,
self.listener.clone(),
Protocol::HTTP,
http.context.id,
http.context.session_address,
- ws_context,
+ websocket_context,
);
pipe.frontend_readiness.event = http.frontend_readiness.event;
pipe.backend_readiness.event = http.backend_readiness.event;
- pipe.set_back_token(back_token);
+ pipe.set_back_token(origin.token);
gauge_add!("protocol.http", -1);
gauge_add!("protocol.ws", 1);
diff --git a/lib/src/https.rs b/lib/src/https.rs
index abaf162c0..58e632ef3 100644
--- a/lib/src/https.rs
+++ b/lib/src/https.rs
@@ -329,11 +329,14 @@ impl HttpsSession {
}
}
- fn upgrade_http(&self, http: Http) -> Option {
+ fn upgrade_http(
+ &self,
+ mut http: Http,
+ ) -> Option {
debug!("https switching to wss");
let front_token = self.frontend_token;
- let back_token = match http.backend_token {
- Some(back_token) => back_token,
+ let origin = match http.origin.take() {
+ Some(origin) => origin,
None => {
warn!(
"Could not upgrade https request on cluster '{:?}' ({:?}) using backend '{:?}' into secure websocket for request '{}'",
@@ -343,7 +346,7 @@ impl HttpsSession {
}
};
- let ws_context = http.websocket_context();
+ let websocket_context = http.websocket_context();
let mut container_frontend_timeout = http.container_frontend_timeout;
let mut container_backend_timeout = http.container_backend_timeout;
container_frontend_timeout.reset();
@@ -357,9 +360,9 @@ impl HttpsSession {
let mut pipe = Pipe::new(
backend_buffer,
- http.context.backend_id,
- http.backend_socket,
- http.backend,
+ Some(origin.backend_id),
+ Some(origin.socket),
+ Some(origin.backend),
Some(container_backend_timeout),
Some(container_frontend_timeout),
http.context.cluster_id,
@@ -367,15 +370,15 @@ impl HttpsSession {
front_token,
http.frontend_socket,
self.listener.clone(),
- Protocol::HTTP,
+ Protocol::HTTPS,
http.context.id,
http.context.session_address,
- ws_context,
+ websocket_context,
);
pipe.frontend_readiness.event = http.frontend_readiness.event;
pipe.backend_readiness.event = http.backend_readiness.event;
- pipe.set_back_token(back_token);
+ pipe.set_back_token(origin.token);
gauge_add!("protocol.https", -1);
gauge_add!("protocol.wss", 1);
diff --git a/lib/src/lib.rs b/lib/src/lib.rs
index 311e83f16..eb858aafe 100644
--- a/lib/src/lib.rs
+++ b/lib/src/lib.rs
@@ -953,7 +953,6 @@ pub struct SessionMetrics {
pub service_start: Option,
pub wait_start: Instant,
- pub backend_id: Option,
pub backend_start: Option,
pub backend_connected: Option,
pub backend_stop: Option,
@@ -971,7 +970,6 @@ impl SessionMetrics {
bout: 0,
service_start: None,
wait_start: Instant::now(),
- backend_id: None,
backend_start: None,
backend_connected: None,
backend_stop: None,
@@ -1072,7 +1070,7 @@ impl SessionMetrics {
time!("request_time", request_time.as_millis());
time!("service_time", service_time.as_millis());
- if let Some(backend_id) = self.backend_id.as_ref() {
+ if let Some(backend_id) = context.backend_id {
if let Some(backend_response_time) = self.backend_response_time() {
record_backend_metrics!(
context.cluster_id.as_str_or("-"),
diff --git a/lib/src/protocol/kawa_h1/answers.rs b/lib/src/protocol/kawa_h1/answers.rs
index 6a8ae2b76..a4b4651ad 100644
--- a/lib/src/protocol/kawa_h1/answers.rs
+++ b/lib/src/protocol/kawa_h1/answers.rs
@@ -1,7 +1,7 @@
use crate::{protocol::http::DefaultAnswer, sozu_command::state::ClusterId};
use kawa::{
- h1::NoCallbacks, AsBuffer, Block, BodySize, Buffer, Chunk, Kawa, Kind, Pair, ParsingPhase,
- ParsingPhaseMarker, StatusLine, Store,
+ h1::NoCallbacks, AsBuffer, Block, BodySize, Buffer, Chunk, Flags, Kawa, Kind, Pair,
+ ParsingPhase, ParsingPhaseMarker, StatusLine, Store,
};
use nom::AsBytes;
use std::{
@@ -11,6 +11,8 @@ use std::{
str::from_utf8_unchecked,
};
+use super::parser::compare_no_case;
+
#[derive(Clone)]
pub struct SharedBuffer(Rc<[u8]>);
@@ -34,6 +36,8 @@ pub enum TemplateError {
InvalidTemplate(ParsingPhase),
#[error("unexpected status code: {0}")]
InvalidStatusCode(u16),
+ #[error("unexpected size info: {0:?}")]
+ InvalidSizeInfo(BodySize),
#[error("streaming is not supported in templates")]
UnsupportedStreaming,
#[error("template variable {0} is not allowed in headers")]
@@ -68,6 +72,7 @@ pub struct Replacement {
// TODO: rename for clarity, for instance HttpAnswerTemplate
pub struct Template {
status: u16,
+ keep_alive: bool,
kawa: DefaultAnswerStream,
body_replacements: Vec,
header_replacements: Vec,
@@ -126,6 +131,9 @@ impl Template {
if !kawa.is_main_phase() {
return Err(TemplateError::InvalidTemplate(kawa.parsing_phase));
}
+ if kawa.body_size != BodySize::Empty {
+ return Err(TemplateError::InvalidSizeInfo(kawa.body_size));
+ }
let status = if let StatusLine::Response { code, .. } = &kawa.detached.status_line {
if let Some(expected_code) = status {
if expected_code != *code {
@@ -141,16 +149,35 @@ impl Template {
let mut header_replacements = Vec::new();
let mut body_replacements = Vec::new();
let mut body_size = 0;
+ let mut keep_alive = true;
let mut used_once = Vec::new();
for mut block in kawa.blocks.into_iter() {
match &mut block {
Block::ChunkHeader(_) => return Err(TemplateError::UnsupportedStreaming),
+ Block::Flags(Flags {
+ end_header: true, ..
+ }) => {
+ header_replacements.push(Replacement {
+ block_index: blocks.len(),
+ typ: ReplacementType::ContentLength,
+ });
+ blocks.push_back(Block::Header(Pair {
+ key: Store::Static(b"Content-Length"),
+ val: Store::Static(b"PLACEHOLDER"),
+ }));
+ blocks.push_back(block);
+ }
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 compare_no_case(key_data, b"connection")
+ && compare_no_case(val_data, b"close")
+ {
+ keep_alive = false;
+ }
if let Some(b'%') = val_data.first() {
for variable in &variables {
if &val_data[1..] == variable.name.as_bytes() {
@@ -168,11 +195,7 @@ impl Template {
}
used_once.push(var_index);
}
- ReplacementType::ContentLength => {
- if let Some(b'%') = key_data.first() {
- *key = Store::new_slice(buf, &key_data[1..]);
- }
- }
+ ReplacementType::ContentLength => {}
}
header_replacements.push(Replacement {
block_index: blocks.len(),
@@ -240,6 +263,7 @@ impl Template {
kawa.blocks = blocks;
Ok(Self {
status,
+ keep_alive,
kawa,
body_replacements,
header_replacements,
@@ -306,7 +330,6 @@ pub struct HttpAnswers {
}
// const HEADERS: &str = "Connection: close\r
-// Content-Length: 0\r
// Sozu-Id: %REQUEST_ID\r
// \r";
// const STYLE: &str = "