Skip to content

Commit

Permalink
Refactor Route, RouteResult and redirection flow
Browse files Browse the repository at this point in the history
Signed-off-by: Eloi DEMOLIS <[email protected]>
  • Loading branch information
Wonshtrum committed Dec 19, 2024
1 parent 663b67f commit 0722e78
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 220 deletions.
7 changes: 7 additions & 0 deletions command/assets/custom_200.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
HTTP/1.1 200 OK
%Content-Length: %CONTENT_LENGTH
Sozu-Id: %REQUEST_ID

<h1>%CLUSTER_ID Custom 200</h1>
<p>original url: %ROUTE</p>
<p>rewritten url: %REDIRECT_LOCATION</p>
4 changes: 2 additions & 2 deletions command/assets/custom_404.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
HTTP/1.1 404 Not Found
Cache-Control: no-cache
Connection: close
Sozu-Id: %SOZU_ID
Sozu-Id: %REQUEST_ID

<h1>My own 404 error page</h1>
<p>Your request %SOZU_ID found no frontend and cannot be redirected.</p>
<p>Your request %REQUEST_ID found no frontend and cannot be redirected.</p>
6 changes: 3 additions & 3 deletions command/assets/custom_503.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
Cache-Control: no-cache
Connection: close
%Content-Length: %CONTENT_LENGTH
Sozu-Id: %SOZU_ID
Sozu-Id: %REQUEST_ID

<h1>MyCluster: 503 Service Unavailable</h1>
<p>No server seems to be alive, could not redirect request %SOZU_ID.</p>
<p>No server seems to be alive, could not redirect request %REQUEST_ID.</p>
<pre>
%DETAILS
%MESSAGE
<pre>
10 changes: 2 additions & 8 deletions lib/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ use std::{
cell::RefCell,
collections::{hash_map::Entry, BTreeMap, HashMap},
io::ErrorKind,
mem,
net::{Shutdown, SocketAddr},
os::unix::io::AsRawFd,
rc::{Rc, Weak},
str::from_utf8_unchecked,
time::{Duration, Instant},
};

Expand All @@ -32,15 +30,11 @@ use crate::{
backends::BackendMap,
pool::Pool,
protocol::{
http::{
answers::HttpAnswers,
parser::{hostname_and_port, Method},
ResponseStream,
},
http::{answers::HttpAnswers, parser::Method, ResponseStream},
proxy_protocol::expect::ExpectProxyProtocol,
Http, Pipe, SessionState,
},
router::{RouteDirection, RouteResult, Router},
router::{RouteResult, Router},
server::{ListenToken, SessionManager},
socket::server_bind,
timer::TimeoutContainer,
Expand Down
10 changes: 3 additions & 7 deletions lib/src/https.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{
net::{Shutdown, SocketAddr as StdSocketAddr},
os::unix::io::AsRawFd,
rc::{Rc, Weak},
str::{from_utf8, from_utf8_unchecked},
str::from_utf8,
sync::Arc,
time::{Duration, Instant},
};
Expand Down Expand Up @@ -53,16 +53,12 @@ use crate::{
pool::Pool,
protocol::{
h2::Http2,
http::{
answers::HttpAnswers,
parser::{hostname_and_port, Method},
ResponseStream,
},
http::{answers::HttpAnswers, parser::Method, ResponseStream},
proxy_protocol::expect::ExpectProxyProtocol,
rustls::TlsHandshake,
Http, Pipe, SessionState,
},
router::{RouteDirection, RouteResult, Router},
router::{RouteResult, Router},
server::{ListenToken, SessionManager},
socket::{server_bind, FrontRustls},
timer::TimeoutContainer,
Expand Down
6 changes: 5 additions & 1 deletion lib/src/protocol/kawa_h1/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ pub struct HttpContext {
pub keep_alive_frontend: bool,
/// the value of the sticky session cookie in the request
pub sticky_session_found: Option<String>,
/// position of the last authentication header, only valid until prepare is called
pub authentication_found: Option<usize>,
// ---------- Status Line
/// the value of the method in the request line
pub method: Option<Method>,
Expand Down Expand Up @@ -135,7 +137,7 @@ impl HttpContext {
let mut has_x_port = false;
let mut has_x_proto = false;
let mut has_connection = false;
for block in &mut request.blocks {
for (i, block) in request.blocks.iter_mut().enumerate() {
match block {
kawa::Block::Header(header) if !header.is_elided() => {
let key = header.key.data(buf);
Expand Down Expand Up @@ -182,6 +184,8 @@ impl HttpContext {
.data_opt(buf)
.and_then(|data| from_utf8(data).ok())
.map(ToOwned::to_owned);
} else if compare_no_case(key, b"Proxy-Authenticate") {
self.authentication_found = Some(i);
}
}
_ => {}
Expand Down
154 changes: 78 additions & 76 deletions lib/src/protocol/kawa_h1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use rusty_ulid::Ulid;
use sozu_command::{
config::MAX_LOOP_ITERATIONS,
logging::EndpointRecord,
proto::command::{Event, EventKind, ListenerType, RedirectScheme},
proto::command::{Event, EventKind, ListenerType, RedirectPolicy, RedirectScheme},
};
// use time::{Duration, Instant};

Expand All @@ -36,7 +36,7 @@ use crate::{
SessionState,
},
retry::RetryPolicy,
router::{RouteDirection, RouteResult},
router::RouteResult,
server::{push_event, CONN_RETRIES},
socket::{stats::socket_rtt, SocketHandler, SocketResult, TransportProtocol},
sozu_command::{logging::LogContext, ready::Ready},
Expand Down Expand Up @@ -245,6 +245,7 @@ impl<Front: SocketHandler, L: ListenerHandler + L7ListenerHandler> Http<Front, L
sticky_name,
sticky_session: None,
sticky_session_found: None,
authentication_found: None,

method: None,
authority: None,
Expand Down Expand Up @@ -1295,91 +1296,92 @@ impl<Front: SocketHandler, L: ListenerHandler + L7ListenerHandler> Http<Front, L
.borrow()
.frontend_from_request(host, path, method);

let route = match route_result {
let RouteResult {
cluster_id,
redirect,
redirect_scheme,
redirect_template,
rewritten_host,
rewritten_path,
rewritten_port,
} = match route_result {
Ok(route) => route,
Err(frontend_error) => {
self.set_answer(DefaultAnswer::Answer404 {});
return Err(RetrieveClusterError::RetrieveFrontend(frontend_error));
}
};

match route {
RouteResult::Deny => {
if let Some(cluster_id) = &cluster_id {
time!(
"frontend_matching_time",
cluster_id,
start.elapsed().as_millis()
);
}

let host = rewritten_host.as_deref().unwrap_or(host);
let path = rewritten_path.as_deref().unwrap_or(path);
let port = rewritten_port.map_or_else(
|| {
port.map_or(String::new(), |port| {
format!(":{}", unsafe { from_utf8_unchecked(port) })
})
},
|port| format!(":{port}"),
);
let is_https = matches!(proxy.borrow().kind(), ListenerType::Https);
let proto = match (redirect_scheme, is_https) {
(RedirectScheme::UseHttp, _) | (RedirectScheme::UseSame, false) => "http",
(RedirectScheme::UseHttps, _) | (RedirectScheme::UseSame, true) => "https",
};

match (cluster_id, redirect, redirect_template) {
(_, RedirectPolicy::Unauthorized, _) | (None, RedirectPolicy::Forward, None) => {
self.set_answer(DefaultAnswer::Answer401 {});
Err(RetrieveClusterError::UnauthorizedRoute)
return Err(RetrieveClusterError::UnauthorizedRoute);
}
RouteResult::Flow {
direction: flow,
rewritten_host,
rewritten_path,
rewritten_port,
} => {
let is_https = matches!(proxy.borrow().kind(), ListenerType::Https);
if let RouteDirection::Forward(cluster_id) = &flow {
time!(
"frontend_matching_time",
cluster_id,
start.elapsed().as_millis()
);
let (https_redirect, https_redirect_port, authentication) = proxy
.borrow()
.clusters()
.get(cluster_id)
.map(|cluster| {
(
cluster.https_redirect,
cluster.https_redirect_port,
None::<()>,
)
})
.unwrap_or((false, None, None));
if !is_https && https_redirect {
let port = https_redirect_port
.map_or(String::new(), |port| format!(":{}", port as u16));
self.set_answer(DefaultAnswer::Answer301 {
location: format!("https://{host}{port}{path}"),
});
return Err(RetrieveClusterError::Redirected);
}
if let Some(authentication) = authentication {
return Err(RetrieveClusterError::UnauthorizedRoute);
}
(_, RedirectPolicy::Permanent, _) => {
self.set_answer(DefaultAnswer::Answer301 {
location: format!("{proto}://{host}{port}{path}"),
});
Err(RetrieveClusterError::Redirected)
}
(_, RedirectPolicy::Temporary, _) => todo!(),
(cluster_id, RedirectPolicy::Forward, Some(name)) => {
let location = format!("{proto}://{host}{port}{path}");
// TODO: this feels wrong
self.context.cluster_id = cluster_id;
self.set_answer(DefaultAnswer::AnswerCustom { name, location });
Err(RetrieveClusterError::Redirected)
}
(Some(cluster_id), RedirectPolicy::Forward, None) => {
let (https_redirect, https_redirect_port, authentication) = proxy
.borrow()
.clusters()
.get(&cluster_id)
.map(|cluster| {
(
cluster.https_redirect,
cluster.https_redirect_port,
None::<()>,
)
})
.unwrap_or((false, None, None));
if !is_https && https_redirect {
let port = rewritten_port
.or_else(|| https_redirect_port.map(|port| port as u16))
.map_or(String::new(), |port| format!(":{port}"));
self.set_answer(DefaultAnswer::Answer301 {
location: format!("https://{host}{port}{path}"),
});
return Err(RetrieveClusterError::Redirected);
}
let host = rewritten_host.as_deref().unwrap_or(host);
let path = rewritten_path.as_deref().unwrap_or(path);
let port = rewritten_port.map_or_else(
|| {
port.map_or(String::new(), |port| {
format!(":{}", unsafe { from_utf8_unchecked(port) })
})
},
|port| format!(":{port}"),
);
match flow {
RouteDirection::Forward(cluster_id) => Ok(cluster_id),
RouteDirection::Permanent(redirect_scheme) => {
let proto = match (redirect_scheme, is_https) {
(RedirectScheme::UseHttp, _) | (RedirectScheme::UseSame, false) => {
"http"
}
(RedirectScheme::UseHttps, _) | (RedirectScheme::UseSame, true) => {
"https"
}
};
self.set_answer(DefaultAnswer::Answer301 {
location: format!("{proto}://{host}{port}{path}"),
});
Err(RetrieveClusterError::Redirected)
}
RouteDirection::Temporary(_) => todo!(),
RouteDirection::Template(cluster_id, name) => {
let location = format!("{host}{port}{path}");
// TODO: this feels wrong
self.context.cluster_id = cluster_id;
self.set_answer(DefaultAnswer::AnswerCustom { name, location });
Err(RetrieveClusterError::Redirected)
}
if let Some(authentication) = authentication {

Check warning on line 1380 in lib/src/protocol/kawa_h1/mod.rs

View workflow job for this annotation

GitHub Actions / Build documentation

unused variable: `authentication`

Check warning on line 1380 in lib/src/protocol/kawa_h1/mod.rs

View workflow job for this annotation

GitHub Actions / Build documentation

unused variable: `authentication`

Check warning on line 1380 in lib/src/protocol/kawa_h1/mod.rs

View workflow job for this annotation

GitHub Actions / Build Sozu 🦀

unused variable: `authentication`

Check warning on line 1380 in lib/src/protocol/kawa_h1/mod.rs

View workflow job for this annotation

GitHub Actions / Test (nightly, true)

unused variable: `authentication`

Check warning on line 1380 in lib/src/protocol/kawa_h1/mod.rs

View workflow job for this annotation

GitHub Actions / Test (nightly, true)

unused variable: `authentication`

Check warning on line 1380 in lib/src/protocol/kawa_h1/mod.rs

View workflow job for this annotation

GitHub Actions / Test (nightly, true)

unused variable: `authentication`

Check warning on line 1380 in lib/src/protocol/kawa_h1/mod.rs

View workflow job for this annotation

GitHub Actions / Test (nightly, true)

unused variable: `authentication`

Check warning on line 1380 in lib/src/protocol/kawa_h1/mod.rs

View workflow job for this annotation

GitHub Actions / Build Sozu 🦀

unused variable: `authentication`

Check warning on line 1380 in lib/src/protocol/kawa_h1/mod.rs

View workflow job for this annotation

GitHub Actions / Test (false, stable)

unused variable: `authentication`

Check warning on line 1380 in lib/src/protocol/kawa_h1/mod.rs

View workflow job for this annotation

GitHub Actions / Test (false, stable)

unused variable: `authentication`

Check warning on line 1380 in lib/src/protocol/kawa_h1/mod.rs

View workflow job for this annotation

GitHub Actions / Test (false, stable)

unused variable: `authentication`

Check warning on line 1380 in lib/src/protocol/kawa_h1/mod.rs

View workflow job for this annotation

GitHub Actions / Test (false, stable)

unused variable: `authentication`

Check warning on line 1380 in lib/src/protocol/kawa_h1/mod.rs

View workflow job for this annotation

GitHub Actions / Test (false, beta)

unused variable: `authentication`

Check warning on line 1380 in lib/src/protocol/kawa_h1/mod.rs

View workflow job for this annotation

GitHub Actions / Test (false, beta)

unused variable: `authentication`

Check warning on line 1380 in lib/src/protocol/kawa_h1/mod.rs

View workflow job for this annotation

GitHub Actions / Test (false, beta)

unused variable: `authentication`

Check warning on line 1380 in lib/src/protocol/kawa_h1/mod.rs

View workflow job for this annotation

GitHub Actions / Test (false, beta)

unused variable: `authentication`
self.set_answer(DefaultAnswer::Answer401 {});
return Err(RetrieveClusterError::UnauthorizedRoute);
}
return Ok(cluster_id);
}
}
}
Expand Down
Loading

0 comments on commit 0722e78

Please sign in to comment.