From 948338311f225732d3968c7d1c45b8c7069a0d56 Mon Sep 17 00:00:00 2001 From: Amit Upadhyay Date: Fri, 3 May 2024 16:58:24 +0530 Subject: [PATCH] wip --- Cargo.lock | 1 + fastn-core/src/commands/serve.rs | 27 +++-- fastn-core/src/config/utils.rs | 88 ---------------- fastn-core/src/http.rs | 4 + fastn-core/src/library2022/processor/http.rs | 23 ++-- fastn-ds/src/lib.rs | 12 ++- fastn-package/Cargo.toml | 1 + fastn-package/src/old_fastn.rs | 105 +++++++++++++++++++ 8 files changed, 145 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 881e09abf7..3ac30e4582 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1700,6 +1700,7 @@ dependencies = [ "futures", "rusqlite", "serde", + "thiserror", "tokio", ] diff --git a/fastn-core/src/commands/serve.rs b/fastn-core/src/commands/serve.rs index b1bfdf5fe7..91cf9b4881 100644 --- a/fastn-core/src/commands/serve.rs +++ b/fastn-core/src/commands/serve.rs @@ -424,21 +424,28 @@ async fn handle_endpoints( None => return None, }; - let url = format!( - "{}/{}", - endpoint.endpoint.trim_end_matches('/'), - req.path() - .trim_start_matches(endpoint.mountpoint.trim_end_matches('/')) - .trim_start_matches('/') - ); - - if url.starts_with("wasm+proxy://") { - return match config.ds.handle_wasm(url, req).await { + if endpoint.is_wasm() { + return match config + .ds + .handle_wasm( + endpoint.wasm_file().unwrap(), + endpoint.wasm_path(req.path()).unwrap(), + req, + ) + .await + { Ok(r) => Some(Ok(fastn_ds::wasm::to_response(r))), Err(e) => return Some(Err(e.into())), }; } + // if url.starts_with("js+proxy://") { + // return match config.ds.handle_js(url, req).await { + // Ok(r) => Some(Ok(fastn_ds::wasm::to_response(r))), + // Err(e) => return Some(Err(e.into())), + // }; + // } + let response = match config .ds .http( diff --git a/fastn-core/src/config/utils.rs b/fastn-core/src/config/utils.rs index f3ff0868f7..63d7acdead 100644 --- a/fastn-core/src/config/utils.rs +++ b/fastn-core/src/config/utils.rs @@ -48,94 +48,6 @@ pub fn trim_package_name(path: &str, package_name: &str) -> Option { None } -// url can be start with /-/package-name/ or -/package-name/ -// It will return url with end-point, if package or dependency contains endpoints in them -// url: /-//api/ => (package-name, endpoints/api/, app or package config) -// url: /-//api/ => (package-name, endpoints/api/, app or package config) -pub fn get_clean_url( - config: &fastn_core::Config, - url: &str, -) -> fastn_core::Result<(url::Url, std::collections::HashMap)> { - if url.starts_with("http") { - return Ok((url::Url::parse(url)?, std::collections::HashMap::new())); - } - - let url = if url.starts_with("/-/") || url.starts_with("-/") { - url.to_string() - } else { - config - .get_mountpoint_sanitized_path(&config.package, url) - .map(|(u, _, _, _)| u) - .unwrap_or_else(|| url.to_string()) // TODO: Error possibly, in that return 404 from proxy - }; - - // This is for current package - if let Some(remaining_url) = trim_package_name(url.as_str(), config.package.name.as_str()) { - if config.package.endpoints.is_empty() { - return Err(fastn_core::Error::GenericError(format!( - "package does not contain the endpoints: {:?}", - config.package.name - ))); - } - - let mut end_point = None; - for e in config.package.endpoints.iter() { - if remaining_url.starts_with(e.mountpoint.as_str()) { - end_point = Some(e.endpoint.to_string()); - break; - } - } - - if end_point.is_none() { - return Err(fastn_core::Error::GenericError(format!( - "No mountpoint matched for url: {}", - remaining_url.as_str() - ))); - } - - return Ok(( - url::Url::parse(format!("{}{}", end_point.unwrap(), remaining_url).as_str())?, - std::collections::HashMap::new(), // TODO: - )); - } - - // Handle logic for apps - for app in config.package.apps.iter() { - if let Some(ep) = &app.end_point { - if let Some(remaining_url) = trim_package_name(url.as_str(), app.package.name.as_str()) - { - let mut app_conf = app.config.clone(); - if let Some(user_id) = &app.user_id { - app_conf.insert("user-id".to_string(), user_id.clone()); - } - return Ok(( - url::Url::parse(format!("{}{}", ep, remaining_url).as_str())?, - app_conf, - )); - } - } - } - - if let Some(e) = config - .package - .endpoints - .iter() - .find(|&endpoint| url.starts_with(&endpoint.mountpoint)) - { - let endpoint_url = e.endpoint.trim_end_matches('/'); - let relative_path = url.trim_start_matches(&e.mountpoint); - let full_url = format!("{}/{}", endpoint_url, relative_path); - return Ok(( - url::Url::parse(&full_url)?, - std::collections::HashMap::new(), - )); - } - - let msg = format!("http-processor: end-point not found url: {}", url); - tracing::error!(msg = msg); - Err(fastn_core::Error::GenericError(msg)) -} - pub(crate) fn is_http_url(url: &str) -> bool { url.starts_with("http") } diff --git a/fastn-core/src/http.rs b/fastn-core/src/http.rs index a52f1e4b12..58f3b5cae1 100644 --- a/fastn-core/src/http.rs +++ b/fastn-core/src/http.rs @@ -138,6 +138,10 @@ pub struct Request { #[async_trait::async_trait] impl fastn_ds::RequestType for Request { + fn path(&self) -> &str { + self.path.as_str() + } + async fn ud(&self, ds: &fastn_ds::DocumentStore) -> Option { self.ud(ds).await } diff --git a/fastn-core/src/library2022/processor/http.rs b/fastn-core/src/library2022/processor/http.rs index a240e40dc7..54edad205d 100644 --- a/fastn-core/src/library2022/processor/http.rs +++ b/fastn-core/src/library2022/processor/http.rs @@ -57,14 +57,7 @@ pub async fn process( } }; - let (mut url, mut conf) = - fastn_core::config::utils::get_clean_url(&req_config.config, url.as_str()).map_err( - |e| ftd::interpreter::Error::ParseError { - message: format!("invalid url: {:?}", e), - doc_id: doc.name.to_string(), - line_number, - }, - )?; + let mut extra_headers = std::collections::HashMap::new(); let mut body = vec![]; for header in headers.0 { @@ -85,7 +78,7 @@ pub async fn process( .to_json_string(doc, true)? { if let Some(key) = fastn_core::http::get_header_key(header.key.as_str()) { - conf.insert(key.to_string(), value.trim_matches('"').to_string()); + extra_headers.insert(key.to_string(), value.trim_matches('"').to_string()); continue; } if method.as_str().eq("post") { @@ -97,7 +90,7 @@ pub async fn process( } } else { if let Some(key) = fastn_core::http::get_header_key(header.key.as_str()) { - conf.insert(key.to_string(), value); + extra_headers.insert(key.to_string(), value); continue; } if method.as_str().eq("post") { @@ -135,13 +128,17 @@ pub async fn process( }); Ok((Ok(r.body.into()), resp_cookies)) } - _ => todo!(), + Err(e) => { + return Err(ftd::interpreter::Error::DSHttpError { + message: format!("{:?}", e), + }) + } } } else if method.as_str().eq("post") { fastn_core::http::http_post_with_cookie( req_config, url.as_str(), - &conf, + &extra_headers, format!("{{{}}}", body.join(",")).as_str(), ) .await @@ -153,7 +150,7 @@ pub async fn process( &req_config.config.ds, &req_config.request, url.as_str(), - &conf, + &extra_headers, false, // disable cache ) .await diff --git a/fastn-ds/src/lib.rs b/fastn-ds/src/lib.rs index efdc324092..27769634d3 100644 --- a/fastn-ds/src/lib.rs +++ b/fastn-ds/src/lib.rs @@ -159,6 +159,7 @@ pub type HttpResponse = ::http::Response; #[async_trait::async_trait] pub trait RequestType { + fn path(&self) -> &str; async fn ud(&self, ds: &fastn_ds::DocumentStore) -> Option; fn headers(&self) -> &reqwest::header::HeaderMap; fn method(&self) -> &str; @@ -379,7 +380,8 @@ impl DocumentStore { pub async fn handle_wasm( &self, - wasm_url: String, + wasm_file: &str, + wasm_path: String, req: &T, ) -> Result where @@ -391,13 +393,13 @@ impl DocumentStore { .map(|(k, v)| (k.as_str().to_string(), v.as_bytes().to_vec())) .collect(); - let wasm_file = wasm_url.strip_prefix("wasm+proxy://").unwrap(); - let wasm_file = wasm_file.split_once(".wasm").unwrap().0; - let module = self.get_wasm(format!("{wasm_file}.wasm").as_str()).await?; + // .unwrap() is okay because the callers of this function should only call if the + // exact string matches + let module = self.get_wasm(wasm_file).await?; Ok(fastn_ds::wasm::process_http_request( ft_sys_shared::Request { - uri: wasm_url, + uri: wasm_path, method: req.method().to_string(), headers, body: req.body().to_vec(), diff --git a/fastn-package/Cargo.toml b/fastn-package/Cargo.toml index 065cc61d1e..5f9f5685b8 100644 --- a/fastn-package/Cargo.toml +++ b/fastn-package/Cargo.toml @@ -12,6 +12,7 @@ async-trait.workspace = true fastn-issues.workspace = true ftd.workspace = true ftd-ast.workspace = true +thiserror.workspace = true rusqlite.workspace = true serde.workspace = true tokio.workspace = true diff --git a/fastn-package/src/old_fastn.rs b/fastn-package/src/old_fastn.rs index 4230c8db15..07eb370af3 100644 --- a/fastn-package/src/old_fastn.rs +++ b/fastn-package/src/old_fastn.rs @@ -72,6 +72,111 @@ pub struct EndpointData { pub user_id: Option, } +#[derive(Debug, thiserror::Error)] +pub enum TFileError { + #[error("endpoint does not start with {0}")] + EndpointDoesNotStartWithPrefix(&'static str), + #[error("file missing")] + FileMissing, +} + +#[derive(Debug, thiserror::Error)] +pub enum TPathError { + #[error("file error {0}")] + FileError(#[from] TFileError), + #[error("path doesn't start with mountpoint")] + PathDoesntStartWithMountpoint, + #[error("endpoint lacks star")] + EndpointLacksStar, + #[error("mountpoint lacks star")] + MountpointLacksStar, +} + +impl EndpointData { + const WASM: (&'static str, &'static str) = ("wasm+proxy://", ".wasm"); + const JS: (&'static str, &'static str) = ("js+proxy://", ".js"); + + pub fn is_wasm(&self) -> bool { + self.is_t(Self::WASM) + } + + pub fn is_js(&self) -> bool { + self.is_t(Self::JS) + } + + fn is_t(&self, t: (&'static str, &'static str)) -> bool { + self.endpoint.starts_with(t.0) + } + + // say we have the following in FASTN.ftd (under -- fastn.url-mappings:) + // /ft-wasm/* -> wasm+proxy://sample.wasm/* + // mountpoint: /ft-wasm/* + // endpoint: wasm+proxy://sample.wasm/* + // we want to extract the sample.wasm from the endpoint + pub fn wasm_file(&self) -> Result<&str, TFileError> { + self.t_file(Self::WASM) + } + + pub fn wasm_path(&self, path: &str) -> Result { + self.t_path(path, Self::WASM) + } + + fn t_file(&self, t: (&'static str, &'static str)) -> Result<&str, TFileError> { + if !self.is_t(t) { + return Err(TFileError::EndpointDoesNotStartWithPrefix(t.0)); + } + + match self.endpoint.find(t.1) { + Some(idx) => Ok(&self.endpoint[t.0.len()..idx]), + None => Err(TFileError::FileMissing), + } + } + + // with: /ft-wasm/* -> wasm+proxy://sample.wasm/foo/bar/* + // and requested path: /ft-wasm/yo/123, we need to return /foo/bar/yo/123 + pub fn t_path( + &self, + path: &str, + t: (&'static str, &'static str), + ) -> Result { + if !self.is_t(t) { + return Err(TPathError::FileError( + TFileError::EndpointDoesNotStartWithPrefix(t.0), + )); + } + + let wasm_file = self.t_file(t)?; // sample.wasm + + let endpoint_rest = // /foo/bar/* + &self.endpoint[self.endpoint.find(wasm_file).unwrap() + wasm_file.len()..]; + + if !endpoint_rest.ends_with("*") { + return Err(TPathError::EndpointLacksStar); + } + let mut endpoint_rest = endpoint_rest.trim_end_matches('*'); // /foo/bar/ + if endpoint_rest.ends_with('/') { + endpoint_rest = &endpoint_rest[..endpoint_rest.len() - 1]; + } + + if !self.mountpoint.ends_with('*') { + return Err(TPathError::MountpointLacksStar); + } + if path.starts_with(&self.mountpoint) { + return Err(TPathError::PathDoesntStartWithMountpoint); + } + + // /ft-wasm/ + let mountpoint = self.mountpoint.trim_end_matches('*'); + let mut path_rest = &path[mountpoint.len()..]; // /ft-wasm/yo/123 -> /yo/123 + + if path_rest.starts_with('/') { + path_rest = &path_rest[1..]; + } + + Ok(format!("{}/{}", endpoint_rest, path_rest)) + } +} + /// PackageTemp is a struct that is used for mapping the `fastn.package` data in FASTN.ftd file. It is /// not used elsewhere in program, it is immediately converted to `fastn_core::Package` struct during /// deserialization process