forked from Deuxfleurs/tricot
implement redirection in https.rs
This commit is contained in:
parent
f11592926b
commit
2b3f934247
2 changed files with 65 additions and 11 deletions
66
src/https.rs
66
src/https.rs
|
@ -24,7 +24,7 @@ use tokio_util::io::{ReaderStream, StreamReader};
|
||||||
use opentelemetry::{metrics, KeyValue};
|
use opentelemetry::{metrics, KeyValue};
|
||||||
|
|
||||||
use crate::cert_store::{CertStore, StoreResolver};
|
use crate::cert_store::{CertStore, StoreResolver};
|
||||||
use crate::proxy_config::{ProxyConfig, ProxyEntry};
|
use crate::proxy_config::{HostDescription, ProxyConfig, ProxyEntry};
|
||||||
use crate::reverse_proxy;
|
use crate::reverse_proxy;
|
||||||
|
|
||||||
const MAX_CONNECTION_LIFETIME: Duration = Duration::from_secs(24 * 3600);
|
const MAX_CONNECTION_LIFETIME: Duration = Duration::from_secs(24 * 3600);
|
||||||
|
@ -272,15 +272,22 @@ async fn select_target_and_proxy(
|
||||||
);
|
);
|
||||||
proxy_to.calls_in_progress.fetch_add(1, Ordering::SeqCst);
|
proxy_to.calls_in_progress.fetch_add(1, Ordering::SeqCst);
|
||||||
|
|
||||||
|
// Forward to backend
|
||||||
debug!("{}{} -> {}", host, path, proxy_to);
|
debug!("{}{} -> {}", host, path, proxy_to);
|
||||||
trace!("Request: {:?}", req);
|
trace!("Request: {:?}", req);
|
||||||
|
|
||||||
let response = match do_proxy(https_config, remote_addr, req, proxy_to).await {
|
let response = if let Some(http_res) = try_redirect(host, path, proxy_to) {
|
||||||
Ok(resp) => resp,
|
// redirection middleware
|
||||||
Err(e) => Response::builder()
|
http_res
|
||||||
.status(StatusCode::BAD_GATEWAY)
|
} else {
|
||||||
.body(Body::from(format!("Proxy error: {}", e)))
|
// proxying to backend
|
||||||
.unwrap(),
|
match do_proxy(https_config, remote_addr, req, proxy_to).await {
|
||||||
|
Ok(resp) => resp,
|
||||||
|
Err(e) => Response::builder()
|
||||||
|
.status(StatusCode::BAD_GATEWAY)
|
||||||
|
.body(Body::from(format!("Proxy error: {}", e)))
|
||||||
|
.unwrap(),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
proxy_to.calls_in_progress.fetch_sub(1, Ordering::SeqCst);
|
proxy_to.calls_in_progress.fetch_sub(1, Ordering::SeqCst);
|
||||||
|
@ -302,6 +309,51 @@ async fn select_target_and_proxy(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn try_redirect(req_host: &str, req_path: &str, proxy_to: &ProxyEntry) -> Option<Response<Body>> {
|
||||||
|
let maybe_redirect = proxy_to.redirects.iter().find(|(src, _, _)| {
|
||||||
|
let mut matched: bool = src.host.matches(req_host);
|
||||||
|
|
||||||
|
if let Some(path) = &src.path_prefix {
|
||||||
|
matched &= req_path.starts_with(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
matched
|
||||||
|
});
|
||||||
|
|
||||||
|
let (src_prefix, dst_prefix, code) = match maybe_redirect {
|
||||||
|
None => return None,
|
||||||
|
Some(redirect) => redirect,
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_host = match &dst_prefix.host {
|
||||||
|
HostDescription::Hostname(h) => h,
|
||||||
|
_ => unreachable!(), // checked when ProxyEntry is created
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_prefix = dst_prefix.path_prefix.as_deref().unwrap_or("");
|
||||||
|
let original_prefix = src_prefix.path_prefix.as_deref().unwrap_or("");
|
||||||
|
let suffix = &req_path[original_prefix.len()..];
|
||||||
|
|
||||||
|
let uri = format!("https://{}{}{}", new_host, new_prefix, suffix);
|
||||||
|
|
||||||
|
let status = match StatusCode::from_u16(*code) {
|
||||||
|
Err(e) => {
|
||||||
|
warn!(
|
||||||
|
"Couldn't redirect {}{} to {} as code {} in invalid: {}",
|
||||||
|
req_host, req_path, uri, code, e
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Ok(sc) => sc,
|
||||||
|
};
|
||||||
|
|
||||||
|
Response::builder()
|
||||||
|
.header("Location", uri.clone())
|
||||||
|
.status(status)
|
||||||
|
.body(Body::from(uri))
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
async fn do_proxy(
|
async fn do_proxy(
|
||||||
https_config: &HttpsConfig,
|
https_config: &HttpsConfig,
|
||||||
remote_addr: SocketAddr,
|
remote_addr: SocketAddr,
|
||||||
|
|
|
@ -106,7 +106,7 @@ pub struct ProxyEntry {
|
||||||
|
|
||||||
/// Try to match all these redirection before forwarding to the backend
|
/// Try to match all these redirection before forwarding to the backend
|
||||||
/// when matching this rule
|
/// when matching this rule
|
||||||
pub redirects: Vec<(UrlPrefix, UrlPrefix, u32)>,
|
pub redirects: Vec<(UrlPrefix, UrlPrefix, u16)>,
|
||||||
|
|
||||||
/// Number of calls in progress, used to deprioritize slow back-ends
|
/// Number of calls in progress, used to deprioritize slow back-ends
|
||||||
pub calls_in_progress: atomic::AtomicI64,
|
pub calls_in_progress: atomic::AtomicI64,
|
||||||
|
@ -246,7 +246,7 @@ enum MatchTag {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum ConfigTag<'a> {
|
enum ConfigTag<'a> {
|
||||||
AddHeader(&'a str, String),
|
AddHeader(&'a str, String),
|
||||||
AddRedirect(UrlPrefix, UrlPrefix, u32),
|
AddRedirect(UrlPrefix, UrlPrefix, u16),
|
||||||
GlobalLb,
|
GlobalLb,
|
||||||
LocalLb,
|
LocalLb,
|
||||||
}
|
}
|
||||||
|
@ -301,14 +301,16 @@ fn parse_tricot_tags(tag: &str) -> Option<ParsedTag> {
|
||||||
let maybe_parsed_code = maybe_raw_code
|
let maybe_parsed_code = maybe_raw_code
|
||||||
.iter()
|
.iter()
|
||||||
.next()
|
.next()
|
||||||
.map(|c| c.parse::<u32>().ok())
|
.map(|c| c.parse::<u16>().ok())
|
||||||
.flatten();
|
.flatten();
|
||||||
let http_code = match maybe_parsed_code {
|
let http_code = match maybe_parsed_code {
|
||||||
Some(301) => 301,
|
Some(301) => 301,
|
||||||
Some(302) => 302,
|
Some(302) => 302,
|
||||||
|
Some(303) => 303,
|
||||||
|
Some(307) => 307,
|
||||||
_ => {
|
_ => {
|
||||||
debug!(
|
debug!(
|
||||||
"tag {} has a missing or invalid http code, set it to 302",
|
"tag {} has a missing or invalid http code, setting it to 302",
|
||||||
tag
|
tag
|
||||||
);
|
);
|
||||||
302
|
302
|
||||||
|
|
Loading…
Reference in a new issue