Merge branch 'tmp/sticky-cookie' into feat/sticky-cookie-feature
This commit is contained in:
commit
d8cadf06af
80 changed files with 4870 additions and 867 deletions
|
|
@ -44,10 +44,7 @@ mod tests {
|
|||
}
|
||||
#[test]
|
||||
fn ipv6_to_canonical() {
|
||||
let socket = SocketAddr::new(
|
||||
IpAddr::V6(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0xdead, 0xbeef)),
|
||||
8080,
|
||||
);
|
||||
let socket = SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0xdead, 0xbeef)), 8080);
|
||||
assert_eq!(socket.to_canonical(), socket);
|
||||
}
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ where
|
|||
Ok(v)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
error!("{e}: {log_data}");
|
||||
let code = StatusCode::from(e);
|
||||
log_data.status_code(&code).output();
|
||||
synthetic_error_response(code)
|
||||
|
|
@ -107,9 +107,11 @@ where
|
|||
let backend_app = match self.app_manager.apps.get(&server_name) {
|
||||
Some(backend_app) => backend_app,
|
||||
None => {
|
||||
let Some(default_server_name) = &self.app_manager.default_server_name else {
|
||||
return Err(HttpError::NoMatchingBackendApp);
|
||||
};
|
||||
let default_server_name = self
|
||||
.app_manager
|
||||
.default_server_name
|
||||
.as_ref()
|
||||
.ok_or(HttpError::NoMatchingBackendApp)?;
|
||||
debug!("Serving by default app");
|
||||
self.app_manager.apps.get(default_server_name).unwrap()
|
||||
}
|
||||
|
|
@ -131,9 +133,7 @@ where
|
|||
// Find reverse proxy for given path and choose one of upstream host
|
||||
// Longest prefix match
|
||||
let path = req.uri().path();
|
||||
let Some(upstream_candidates) = backend_app.path_manager.get(path) else {
|
||||
return Err(HttpError::NoUpstreamCandidates);
|
||||
};
|
||||
let upstream_candidates = backend_app.path_manager.get(path).ok_or(HttpError::NoUpstreamCandidates)?;
|
||||
|
||||
// Upgrade in request header
|
||||
let upgrade_in_request = extract_upgrade(req.headers());
|
||||
|
|
@ -147,19 +147,17 @@ where
|
|||
let req_on_upgrade = hyper::upgrade::on(&mut req);
|
||||
|
||||
// Build request from destination information
|
||||
let _context = match self.generate_request_forwarded(
|
||||
&client_addr,
|
||||
&listen_addr,
|
||||
&mut req,
|
||||
&upgrade_in_request,
|
||||
upstream_candidates,
|
||||
tls_enabled,
|
||||
) {
|
||||
Err(e) => {
|
||||
return Err(HttpError::FailedToGenerateUpstreamRequest(e.to_string()));
|
||||
}
|
||||
Ok(v) => v,
|
||||
};
|
||||
let _context = self
|
||||
.generate_request_forwarded(
|
||||
&client_addr,
|
||||
&listen_addr,
|
||||
&mut req,
|
||||
&upgrade_in_request,
|
||||
upstream_candidates,
|
||||
tls_enabled,
|
||||
)
|
||||
.map_err(|e| HttpError::FailedToGenerateUpstreamRequest(e.to_string()))?;
|
||||
|
||||
debug!(
|
||||
"Request to be forwarded: [uri {}, method: {}, version {:?}, headers {:?}]",
|
||||
req.uri(),
|
||||
|
|
@ -173,12 +171,12 @@ where
|
|||
|
||||
//////////////
|
||||
// Forward request to a chosen backend
|
||||
let mut res_backend = match self.forwarder.request(req).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return Err(HttpError::FailedToGetResponseFromBackend(e.to_string()));
|
||||
}
|
||||
};
|
||||
let mut res_backend = self
|
||||
.forwarder
|
||||
.request(req)
|
||||
.await
|
||||
.map_err(|e| HttpError::FailedToGetResponseFromBackend(e.to_string()))?;
|
||||
|
||||
//////////////
|
||||
// Process reverse proxy context generated during the forwarding request generation.
|
||||
#[cfg(feature = "sticky-cookie")]
|
||||
|
|
@ -191,16 +189,16 @@ where
|
|||
|
||||
if res_backend.status() != StatusCode::SWITCHING_PROTOCOLS {
|
||||
// Generate response to client
|
||||
if let Err(e) = self.generate_response_forwarded(&mut res_backend, backend_app) {
|
||||
return Err(HttpError::FailedToGenerateDownstreamResponse(e.to_string()));
|
||||
}
|
||||
self
|
||||
.generate_response_forwarded(&mut res_backend, backend_app)
|
||||
.map_err(|e| HttpError::FailedToGenerateDownstreamResponse(e.to_string()))?;
|
||||
return Ok(res_backend);
|
||||
}
|
||||
|
||||
// Handle StatusCode::SWITCHING_PROTOCOLS in response
|
||||
let upgrade_in_response = extract_upgrade(res_backend.headers());
|
||||
let should_upgrade = match (upgrade_in_request.as_ref(), upgrade_in_response.as_ref()) {
|
||||
(Some(u_req), Some(u_res)) => u_req.to_ascii_lowercase() == u_res.to_ascii_lowercase(),
|
||||
(Some(u_req), Some(u_res)) => u_req.eq_ignore_ascii_case(u_res),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use super::{handler_main::HandlerContext, utils_headers::*, utils_request::update_request_line, HttpMessageHandler};
|
||||
use super::{HttpMessageHandler, handler_main::HandlerContext, utils_headers::*, utils_request::update_request_line};
|
||||
use crate::{
|
||||
backend::{BackendApp, UpstreamCandidates},
|
||||
constants::RESPONSE_HEADER_SERVER,
|
||||
log::*,
|
||||
};
|
||||
use anyhow::{anyhow, ensure, Result};
|
||||
use http::{header, HeaderValue, Request, Response, Uri};
|
||||
use anyhow::{Result, anyhow, ensure};
|
||||
use http::{HeaderValue, Request, Response, Uri, header};
|
||||
use hyper_util::client::legacy::connect::Connect;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
|
|
@ -66,17 +66,19 @@ where
|
|||
upstream_candidates: &UpstreamCandidates,
|
||||
tls_enabled: bool,
|
||||
) -> Result<HandlerContext> {
|
||||
debug!("Generate request to be forwarded");
|
||||
trace!("Generate request to be forwarded");
|
||||
|
||||
// Add te: trailer if contained in original request
|
||||
let contains_te_trailers = {
|
||||
if let Some(te) = req.headers().get(header::TE) {
|
||||
te.as_bytes()
|
||||
.split(|v| v == &b',' || v == &b' ')
|
||||
.any(|x| x == "trailers".as_bytes())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
req
|
||||
.headers()
|
||||
.get(header::TE)
|
||||
.map(|te| {
|
||||
te.as_bytes()
|
||||
.split(|v| v == &b',' || v == &b' ')
|
||||
.any(|x| x == "trailers".as_bytes())
|
||||
})
|
||||
.unwrap_or(false)
|
||||
};
|
||||
|
||||
let original_uri = req.uri().to_string();
|
||||
|
|
@ -136,11 +138,7 @@ where
|
|||
let new_uri = Uri::builder()
|
||||
.scheme(upstream_chosen.uri.scheme().unwrap().as_str())
|
||||
.authority(upstream_chosen.uri.authority().unwrap().as_str());
|
||||
let org_pq = match req.uri().path_and_query() {
|
||||
Some(pq) => pq.to_string(),
|
||||
None => "/".to_string(),
|
||||
}
|
||||
.into_bytes();
|
||||
let org_pq = req.uri().path_and_query().map(|pq| pq.as_str()).unwrap_or("/").as_bytes();
|
||||
|
||||
// replace some parts of path if opt_replace_path is enabled for chosen upstream
|
||||
let new_pq = match &upstream_candidates.replace_path {
|
||||
|
|
@ -155,7 +153,7 @@ where
|
|||
new_pq.extend_from_slice(&org_pq[matched_path.len()..]);
|
||||
new_pq
|
||||
}
|
||||
None => org_pq,
|
||||
None => org_pq.to_vec(),
|
||||
};
|
||||
*req.uri_mut() = new_uri.path_and_query(new_pq).build()?;
|
||||
|
||||
|
|
|
|||
|
|
@ -34,11 +34,7 @@ impl<T> From<&http::Request<T>> for HttpMessageLog {
|
|||
client_addr: "".to_string(),
|
||||
method: req.method().to_string(),
|
||||
host: header_mapper(header::HOST),
|
||||
p_and_q: req
|
||||
.uri()
|
||||
.path_and_query()
|
||||
.map_or_else(|| "", |v| v.as_str())
|
||||
.to_string(),
|
||||
p_and_q: req.uri().path_and_query().map_or_else(|| "", |v| v.as_str()).to_string(),
|
||||
version: req.version(),
|
||||
uri_scheme: req.uri().scheme_str().unwrap_or("").to_string(),
|
||||
uri_host: req.uri().host().unwrap_or("").to_string(),
|
||||
|
|
@ -50,6 +46,33 @@ impl<T> From<&http::Request<T>> for HttpMessageLog {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for HttpMessageLog {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{} <- {} -- {} {} {:?} -- {} -- {} \"{}\", \"{}\" \"{}\"",
|
||||
if !self.host.is_empty() {
|
||||
self.host.as_str()
|
||||
} else {
|
||||
self.uri_host.as_str()
|
||||
},
|
||||
self.client_addr,
|
||||
self.method,
|
||||
self.p_and_q,
|
||||
self.version,
|
||||
self.status,
|
||||
if !self.uri_scheme.is_empty() && !self.uri_host.is_empty() {
|
||||
format!("{}://{}", self.uri_scheme, self.uri_host)
|
||||
} else {
|
||||
"".to_string()
|
||||
},
|
||||
self.ua,
|
||||
self.xff,
|
||||
self.upstream
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpMessageLog {
|
||||
pub fn client_addr(&mut self, client_addr: &SocketAddr) -> &mut Self {
|
||||
self.client_addr = client_addr.to_canonical().to_string();
|
||||
|
|
@ -74,26 +97,8 @@ impl HttpMessageLog {
|
|||
|
||||
pub fn output(&self) {
|
||||
info!(
|
||||
"{} <- {} -- {} {} {:?} -- {} -- {} \"{}\", \"{}\" \"{}\"",
|
||||
if !self.host.is_empty() {
|
||||
self.host.as_str()
|
||||
} else {
|
||||
self.uri_host.as_str()
|
||||
},
|
||||
self.client_addr,
|
||||
self.method,
|
||||
self.p_and_q,
|
||||
self.version,
|
||||
self.status,
|
||||
if !self.uri_scheme.is_empty() && !self.uri_host.is_empty() {
|
||||
format!("{}://{}", self.uri_scheme, self.uri_host)
|
||||
} else {
|
||||
"".to_string()
|
||||
},
|
||||
self.ua,
|
||||
self.xff,
|
||||
self.upstream,
|
||||
// self.tls_server_name
|
||||
name: crate::constants::log_event_names::ACCESS_LOG,
|
||||
"{}", self
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ impl From<HttpError> for StatusCode {
|
|||
HttpError::FailedToAddSetCookeInResponse(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
HttpError::FailedToGenerateDownstreamResponse(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
HttpError::FailedToUpgrade(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
HttpError::FailedToGetResponseFromBackend(_) => StatusCode::BAD_GATEWAY,
|
||||
// HttpError::NoUpgradeExtensionInRequest => StatusCode::BAD_REQUEST,
|
||||
// HttpError::NoUpgradeExtensionInResponse => StatusCode::BAD_GATEWAY,
|
||||
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use super::http_result::{HttpError, HttpResult};
|
||||
use crate::{
|
||||
error::*,
|
||||
hyper_ext::body::{empty, ResponseBody},
|
||||
hyper_ext::body::{ResponseBody, empty},
|
||||
name_exp::ServerName,
|
||||
};
|
||||
use http::{Request, Response, StatusCode, Uri};
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ use crate::{
|
|||
backend::{UpstreamCandidates, UpstreamOption},
|
||||
log::*,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::{Result, anyhow, ensure};
|
||||
use bytes::BufMut;
|
||||
use http::{header, HeaderMap, HeaderName, HeaderValue, Uri};
|
||||
use http::{HeaderMap, HeaderName, HeaderValue, Uri, header};
|
||||
use std::{borrow::Cow, net::SocketAddr};
|
||||
|
||||
#[cfg(feature = "sticky-cookie")]
|
||||
|
|
@ -238,10 +238,9 @@ pub(super) fn add_forwarding_header(
|
|||
pub(super) fn remove_connection_header(headers: &mut HeaderMap) {
|
||||
if let Some(values) = headers.get(header::CONNECTION) {
|
||||
if let Ok(v) = values.clone().to_str() {
|
||||
for m in v.split(',') {
|
||||
if !m.is_empty() {
|
||||
headers.remove(m.trim());
|
||||
}
|
||||
let keys = v.split(',').map(|m| m.trim()).filter(|m| !m.is_empty());
|
||||
for m in keys {
|
||||
headers.remove(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -274,13 +273,11 @@ pub(super) fn extract_upgrade(headers: &HeaderMap) -> Option<String> {
|
|||
.to_str()
|
||||
.unwrap_or("")
|
||||
.split(',')
|
||||
.any(|w| w.trim().to_ascii_lowercase() == header::UPGRADE.as_str().to_ascii_lowercase())
|
||||
.any(|w| w.trim().eq_ignore_ascii_case(header::UPGRADE.as_str()))
|
||||
{
|
||||
if let Some(u) = headers.get(header::UPGRADE) {
|
||||
if let Ok(m) = u.to_str() {
|
||||
debug!("Upgrade in request header: {}", m);
|
||||
return Some(m.to_owned());
|
||||
}
|
||||
if let Some(Ok(m)) = headers.get(header::UPGRADE).map(|u| u.to_str()) {
|
||||
debug!("Upgrade in request header: {}", m);
|
||||
return Some(m.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ use crate::{
|
|||
backend::{Upstream, UpstreamCandidates, UpstreamOption},
|
||||
log::*,
|
||||
};
|
||||
use anyhow::{anyhow, ensure, Result};
|
||||
use http::{header, uri::Scheme, Request, Version};
|
||||
use anyhow::{Result, anyhow, ensure};
|
||||
use http::{Request, Version, header, uri::Scheme};
|
||||
|
||||
/// Trait defining parser of hostname
|
||||
/// Inspect and extract hostname from either the request HOST header or request line
|
||||
|
|
@ -59,6 +59,18 @@ pub(super) fn update_request_line<B>(
|
|||
upstream_chosen: &Upstream,
|
||||
upstream_candidates: &UpstreamCandidates,
|
||||
) -> anyhow::Result<()> {
|
||||
// If request is grpc, HTTP/2 is required
|
||||
if req
|
||||
.headers()
|
||||
.get(header::CONTENT_TYPE)
|
||||
.map(|v| v.as_bytes().starts_with(b"application/grpc"))
|
||||
== Some(true)
|
||||
{
|
||||
debug!("Must be http/2 for gRPC request.");
|
||||
*req.version_mut() = Version::HTTP_2;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// If not specified (force_httpXX_upstream) and https, version is preserved except for http/3
|
||||
if upstream_chosen.uri.scheme() == Some(&Scheme::HTTP) {
|
||||
// Change version to http/1.1 when destination scheme is http
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue