Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fastn_ds::http() #1805

Merged
merged 3 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 35 additions & 32 deletions fastn-core/src/commands/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ async fn serve_file(
false,
only_js,
)
.await
.await
{
Ok(r) => r.into(),
Err(e) => {
Expand Down Expand Up @@ -194,7 +194,7 @@ pub async fn serve_helper(

match (req.method().to_lowercase().as_str(), req.path()) {
(_, t) if t.starts_with("/-/auth/") => {
return fastn_core::auth::routes::handle_auth(req, &mut req_config, config).await
return fastn_core::auth::routes::handle_auth(req, &mut req_config, config).await;
}
("get", "/-/clear-cache/") => return clear_cache(config, req).await,
("get", "/-/poll/") => return fastn_core::watcher::poll().await,
Expand Down Expand Up @@ -477,17 +477,17 @@ async fn handle_static_route(
fn generate_dark_image_path(path: &str) -> Option<String> {
match path.rsplit_once('.') {
Some((remaining, ext))
if mime_guess::MimeGuess::from_ext(ext)
.first_or_octet_stream()
.to_string()
.starts_with("image/") =>
{
Some(if remaining.ends_with("-dark") {
format!("{}.{}", remaining.trim_end_matches("-dark"), ext)
} else {
format!("{}-dark.{}", remaining, ext)
})
}
if mime_guess::MimeGuess::from_ext(ext)
.first_or_octet_stream()
.to_string()
.starts_with("image/") =>
{
Some(if remaining.ends_with("-dark") {
format!("{}.{}", remaining.trim_end_matches("-dark"), ext)
} else {
format!("{}-dark.{}", remaining, ext)
})
}
_ => None,
}
}
Expand Down Expand Up @@ -529,22 +529,25 @@ async fn handle_endpoints(
};

Some(
fastn_core::proxy::get_out(
url::Url::parse(
format!(
"{}/{}",
endpoint.endpoint.trim_end_matches('/'),
req.path()
.trim_start_matches(endpoint.mountpoint.trim_end_matches('/'))
.trim_start_matches('/')
config
.ds
.http(
url::Url::parse(
format!(
"{}/{}",
endpoint.endpoint.trim_end_matches('/'),
req.path()
.trim_start_matches(endpoint.mountpoint.trim_end_matches('/'))
.trim_start_matches('/')
)
.as_str(),
)
.as_str(),
.unwrap(),
req,
&std::collections::HashMap::new(),
)
.unwrap(),
req,
&std::collections::HashMap::new(),
)
.await,
.await
.map_err(fastn_core::Error::DSHttpError),
)
}

Expand Down Expand Up @@ -594,10 +597,10 @@ pub async fn listen(
You can try without providing port, it will automatically pick unused port."#,
x.to_string().red()
))
.unwrap_or_else(|| {
"Tried picking port between port 8000 to 9000, none are available :-("
.to_string()
})
.unwrap_or_else(|| {
"Tried picking port between port 8000 to 9000, none are available :-("
.to_string()
})
);
std::process::exit(2);
}
Expand All @@ -612,7 +615,7 @@ You can try without providing port, it will automatically pick unused port."#,
actix_web::middleware::Logger::new(
r#""%r" %Ts %s %b %a "%{Referer}i" "%{User-Agent}i""#,
)
.log_target(""),
.log_target(""),
)
.route("/{path:.*}", actix_web::web::route().to(route))
};
Expand Down
5 changes: 4 additions & 1 deletion fastn-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,12 @@ pub enum Error {
#[error("ds::RemoveError: {}", _0)]
DSRemoveError(#[from] fastn_ds::RemoveError),

#[error("ds::RemoveError: {}", _0)]
#[error("ds::RenameError: {}", _0)]
DSRenameError(#[from] fastn_ds::RenameError),

#[error("ds::HttpError: {}", _0)]
DSHttpError(#[from] fastn_ds::HttpError),

#[error("AssertError: {message}")]
AssertError { message: String },

Expand Down
93 changes: 36 additions & 57 deletions fastn-core/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,42 @@ pub struct Request {
// path_params: Vec<(String, )>
}

impl fastn_ds::RequestType for Request {
fn headers(&self) -> &reqwest::header::HeaderMap {
&self.headers
}

fn method(&self) -> &str {
self.method.as_str()
}

fn query_string(&self) -> &str {
self.query_string.as_str()
}

fn get_ip(&self) -> Option<String> {
self.ip.clone()
}

fn cookies_string(&self) -> Option<String> {
if self.cookies.is_empty() {
return None;
}
Some(
self.cookies()
.iter()
// TODO: check if extra escaping is needed
.map(|(k, v)| format!("{}={}", k, v).replace(';', "%3B"))
.collect::<Vec<_>>()
.join(";"),
)
}

fn body(&self) -> &[u8] {
&self.body
}
}

impl Request {
//pub fn get_named_params() -> {}
pub fn full_path(&self) -> String {
Expand Down Expand Up @@ -359,63 +395,6 @@ impl Request {
}
}

pub(crate) struct ResponseBuilder {
// headers: std::collections::HashMap<String, String>,
// code: u16,
// remaining
}

// We will no do stream, data is going to less from services
impl ResponseBuilder {
// chain implementation
// .build
// response from string, json, bytes etc

pub async fn from_reqwest(response: reqwest::Response) -> fastn_core::http::Response {
let status = response.status();

// Remove `Connection` as per
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection#Directives
let mut response_builder = actix_web::HttpResponse::build(status);
for header in response
.headers()
.iter()
.filter(|(h, _)| *h != "connection")
{
response_builder.insert_header(header);
}

// if status == actix_web::http::StatusCode::FOUND && false {
// response_builder.status(actix_web::http::StatusCode::OK);
// if let Some(location) = response.headers().get(actix_web::http::header::LOCATION) {
// let redirect = location.to_str().unwrap();
// let path = if redirect.trim_matches('/').is_empty() {
// format!("/-/{}/", package_name)
// } else {
// // if it contains query-params so url should not end with /
// if redirect.contains('?') {
// format!("/-/{}/{}", package_name, redirect.trim_matches('/'))
// } else {
// format!("/-/{}/{}/", package_name, redirect.trim_matches('/'))
// }
// };
// let t = serde_json::json!({"redirect": path.as_str()}).to_string();
// return response_builder.body(t);
// }
// }

let content = match response.bytes().await {
Ok(b) => b,
Err(e) => {
return actix_web::HttpResponse::from(actix_web::error::ErrorInternalServerError(
fastn_core::Error::HttpError(e),
))
}
};
response_builder.body(content)
}
}

pub(crate) fn url_regex() -> regex::Regex {
regex::Regex::new(
r"((([A-Za-z]{3,9}:(?://)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:/[\+~%/.\w_]*)?\??(?:[-\+=&;%@.\w_]*)\#?(?:[\w]*))?)"
Expand Down
87 changes: 0 additions & 87 deletions fastn-core/src/proxy.rs
Original file line number Diff line number Diff line change
@@ -1,88 +1 @@
fn client_builder() -> reqwest::Client {
// TODO: Connection Pool, It by default holds the connection pool internally
reqwest::ClientBuilder::new()
.http2_adaptive_window(true)
.tcp_keepalive(std::time::Duration::new(150, 0))
.tcp_nodelay(true)
.connect_timeout(std::time::Duration::new(150, 0))
.connection_verbose(true)
.redirect(reqwest::redirect::Policy::none())
.build()
.unwrap()
}

static CLIENT: once_cell::sync::Lazy<std::sync::Arc<reqwest::Client>> =
once_cell::sync::Lazy::new(|| std::sync::Arc::new(client_builder()));

// This method will connect client request to the out of the world
#[tracing::instrument(skip(req, extra_headers))]
pub(crate) async fn get_out(
url: url::Url,
req: &fastn_core::http::Request,
extra_headers: &std::collections::HashMap<String, String>,
) -> fastn_core::Result<fastn_core::http::Response> {
let headers = req.headers();

let mut proxy_request = reqwest::Request::new(
match req.method() {
"GET" => reqwest::Method::GET,
"POST" => reqwest::Method::POST,
"PUT" => reqwest::Method::PUT,
"DELETE" => reqwest::Method::DELETE,
"PATCH" => reqwest::Method::PATCH,
"HEAD" => reqwest::Method::HEAD,
"OPTIONS" => reqwest::Method::OPTIONS,
"TRACE" => reqwest::Method::TRACE,
"CONNECT" => reqwest::Method::CONNECT,
_ => reqwest::Method::GET,
},
reqwest::Url::parse(
format!(
"{}/{}",
url.as_str().trim_end_matches('/'),
if req.query_string().is_empty() {
"".to_string()
} else {
format!("?{}", req.query_string())
}
)
.as_str(),
)?,
);

*proxy_request.headers_mut() = headers.to_owned();

for (header_key, header_value) in extra_headers {
proxy_request.headers_mut().insert(
reqwest::header::HeaderName::from_bytes(header_key.as_bytes()).unwrap(),
reqwest::header::HeaderValue::from_str(header_value.as_str()).unwrap(),
);
}

proxy_request.headers_mut().insert(
reqwest::header::USER_AGENT,
reqwest::header::HeaderValue::from_static("fastn"),
);

if let Some(ip) = req.get_ip() {
proxy_request.headers_mut().insert(
reqwest::header::FORWARDED,
reqwest::header::HeaderValue::from_str(ip.as_str()).unwrap(),
);
}

if let Some(cookies) = req.cookies_string() {
proxy_request.headers_mut().insert(
reqwest::header::COOKIE,
reqwest::header::HeaderValue::from_str(cookies.as_str()).unwrap(),
);
}

for header in fastn_core::utils::ignore_headers() {
proxy_request.headers_mut().remove(header);
}

*proxy_request.body_mut() = Some(req.body().to_vec().into());

Ok(fastn_core::http::ResponseBuilder::from_reqwest(CLIENT.execute(proxy_request).await?).await)
}
4 changes: 4 additions & 0 deletions fastn-ds/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ camino.workspace = true
ignore.workspace = true
dirs.workspace = true
tracing.workspace = true
reqwest.workspace = true
once_cell.workspace = true
url.workspace = true
regex.workspace = true
44 changes: 44 additions & 0 deletions fastn-ds/src/http.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
fn client_builder() -> reqwest::Client {
// TODO: Connection Pool, It by default holds the connection pool internally
reqwest::ClientBuilder::new()
.http2_adaptive_window(true)
.tcp_keepalive(std::time::Duration::new(150, 0))
.tcp_nodelay(true)
.connect_timeout(std::time::Duration::new(150, 0))
.connection_verbose(true)
.redirect(reqwest::redirect::Policy::none())
.build()
.unwrap()
}

pub static CLIENT: once_cell::sync::Lazy<std::sync::Arc<reqwest::Client>> =
once_cell::sync::Lazy::new(|| std::sync::Arc::new(client_builder()));

pub(crate) struct ResponseBuilder {}

impl ResponseBuilder {
pub async fn from_reqwest(response: reqwest::Response) -> fastn_ds::HttpResponse {
let status = response.status();

// Remove `Connection` as per
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection#Directives
let mut response_builder = actix_web::HttpResponse::build(status);
for header in response
.headers()
.iter()
.filter(|(h, _)| *h != "connection")
{
response_builder.insert_header(header);
}

let content = match response.bytes().await {
Ok(b) => b,
Err(e) => {
return actix_web::HttpResponse::from(actix_web::error::ErrorInternalServerError(
fastn_ds::HttpError::HttpError(e),
))
}
};
response_builder.body(content)
}
}
Loading
Loading