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

http processor to properly handle endpoints #1854

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

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

27 changes: 17 additions & 10 deletions fastn-core/src/commands/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
88 changes: 0 additions & 88 deletions fastn-core/src/config/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,94 +48,6 @@ pub fn trim_package_name(path: &str, package_name: &str) -> Option<String> {
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: /-/<package-name>/api/ => (package-name, endpoints/api/, app or package config)
// url: /-/<package-name>/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<String, String>)> {
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")
}
4 changes: 4 additions & 0 deletions fastn-core/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ft_sys_shared::UserData> {
self.ud(ds).await
}
Expand Down
23 changes: 10 additions & 13 deletions fastn-core/src/library2022/processor/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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") {
Expand All @@ -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") {
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
12 changes: 7 additions & 5 deletions fastn-ds/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ pub type HttpResponse = ::http::Response<bytes::Bytes>;

#[async_trait::async_trait]
pub trait RequestType {
fn path(&self) -> &str;
async fn ud(&self, ds: &fastn_ds::DocumentStore) -> Option<ft_sys_shared::UserData>;
fn headers(&self) -> &reqwest::header::HeaderMap;
fn method(&self) -> &str;
Expand Down Expand Up @@ -379,7 +380,8 @@ impl DocumentStore {

pub async fn handle_wasm<T>(
&self,
wasm_url: String,
wasm_file: &str,
wasm_path: String,
req: &T,
) -> Result<ft_sys_shared::Request, HttpError>
where
Expand All @@ -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(),
Expand Down
1 change: 1 addition & 0 deletions fastn-package/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
105 changes: 105 additions & 0 deletions fastn-package/src/old_fastn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,111 @@ pub struct EndpointData {
pub user_id: Option<bool>,
}

#[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<String, TPathError> {
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<String, TPathError> {
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
Expand Down
Loading