Skip to content

Commit

Permalink
Merge pull request #10 from uqbar-dao/wg/serve-ui
Browse files Browse the repository at this point in the history
Added functions to serve UI
  • Loading branch information
dr-frmr authored Dec 20, 2023
2 parents f00920b + b086852 commit fe0de63
Show file tree
Hide file tree
Showing 4 changed files with 254 additions and 4 deletions.
32 changes: 32 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ serde_json = "1.0"
rand = "0.8"
thiserror = "1.0"
url = "2.4.1"
mime_guess = "2.0"
wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "efcc759" }
201 changes: 198 additions & 3 deletions src/http.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use crate::kernel_types::Payload;
use crate::{Message, Request as uqRequest, Response as uqResponse};
use crate::kernel_types::{FileType, Payload, VfsAction, VfsRequest, VfsResponse};
use crate::{
get_payload, Address, Message, Payload as uqPayload, ProcessId, Request as uqRequest,
Response as uqResponse,
};
pub use http::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::collections::{HashMap, VecDeque};
use std::path::Path;
use thiserror::Error;

//
Expand Down Expand Up @@ -377,3 +381,194 @@ pub fn send_request_await_response(
}),
}
}

pub fn get_mime_type(filename: &str) -> String {
let file_path = Path::new(filename);

let extension = file_path
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("octet-stream");

mime_guess::from_ext(extension)
.first_or_octet_stream()
.to_string()
}

// Serve index.html
pub fn serve_index_html(our: &Address, directory: &str) -> anyhow::Result<(), anyhow::Error> {
let _ = uqRequest::new()
.target(Address::from_str("our@vfs:sys:uqbar")?)
.ipc(serde_json::to_vec(&VfsRequest {
path: format!(
"/{}/pkg/{}/index.html",
our.package_id().to_string(),
directory
),
action: VfsAction::Read,
})?)
.send_and_await_response(5)?;

let Some(payload) = get_payload() else {
return Err(anyhow::anyhow!("serve_index_html: no index.html payload"));
};

let index = String::from_utf8(payload.bytes)?;

// index.html will be served from the root path of your app
bind_http_static_path(
"/",
true,
false,
Some("text/html".to_string()),
index.to_string().as_bytes().to_vec(),
)?;

Ok(())
}

// Serve static files by binding all of them statically, including index.html
pub fn serve_ui(our: &Address, directory: &str) -> anyhow::Result<(), anyhow::Error> {
serve_index_html(our, directory)?;

let initial_path = format!("{}/pkg/{}", our.package_id().to_string(), directory);

let mut queue = VecDeque::new();
queue.push_back(initial_path.clone());

while let Some(path) = queue.pop_front() {
let directory_response = uqRequest::new()
.target(Address::from_str("our@vfs:sys:uqbar")?)
.ipc(serde_json::to_vec(&VfsRequest {
path,
action: VfsAction::ReadDir,
})?)
.send_and_await_response(5)?;

let Ok(directory_response) = directory_response else {
return Err(anyhow::anyhow!("serve_ui: no response for path"));
};

let directory_ipc = serde_json::from_slice::<VfsResponse>(&directory_response.ipc())?;

// Determine if it's a file or a directory and handle appropriately
match directory_ipc {
VfsResponse::ReadDir(directory_info) => {
for entry in directory_info {
match entry.file_type {
// If it's a file, serve it statically
FileType::File => {
if format!("{}/index.html", initial_path.trim_start_matches("/"))
== entry.path
{
continue;
}

let _ = uqRequest::new()
.target(Address::from_str("our@vfs:sys:uqbar")?)
.ipc(serde_json::to_vec(&VfsRequest {
path: entry.path.clone(),
action: VfsAction::Read,
})?)
.send_and_await_response(5)?;

let Some(payload) = get_payload() else {
return Err(anyhow::anyhow!(
"serve_ui: no payload for {}",
entry.path
));
};

let content_type = get_mime_type(&entry.path);

bind_http_static_path(
entry.path.replace(&initial_path, ""),
true, // Must be authenticated
false, // Is not local-only
Some(content_type),
payload.bytes,
)?;
}
FileType::Directory => {
// Push the directory onto the queue
queue.push_back(entry.path);
}
_ => {}
}
}
}
_ => {
return Err(anyhow::anyhow!(
"serve_ui: unexpected response for path: {:?}",
directory_ipc
))
}
};
}

Ok(())
}

pub fn handle_ui_asset_request(
our: &Address,
directory: &str,
path: &str,
) -> anyhow::Result<(), anyhow::Error> {
let parts: Vec<&str> = path.split(&our.process.to_string()).collect();
let after_process = parts.get(1).unwrap_or(&"");

let target_path = format!("{}/{}", directory, after_process.trim_start_matches('/'));

let _ = uqRequest::new()
.target(Address::from_str("our@vfs:sys:uqbar")?)
.ipc(serde_json::to_vec(&VfsRequest {
path: format!("{}/pkg/{}", our.package_id().to_string(), target_path),
action: VfsAction::Read,
})?)
.send_and_await_response(5)?;

let mut headers = HashMap::new();
let content_type = get_mime_type(&path);
headers.insert("Content-Type".to_string(), content_type);

uqResponse::new()
.ipc(
serde_json::json!(HttpResponse {
status: 200,
headers,
})
.to_string()
.as_bytes()
.to_vec(),
)
.inherit(true)
.send()?;

Ok(())
}

pub fn send_ws_push(
node: String,
channel_id: u32,
message_type: WsMessageType,
payload: uqPayload,
) -> anyhow::Result<()> {
uqRequest::new()
.target(Address::new(
node,
ProcessId::from_str("http_server:sys:uqbar").unwrap(),
))
.ipc(
serde_json::json!(HttpServerRequest::WebSocketPush {
channel_id,
message_type,
})
.to_string()
.as_bytes()
.to_vec(),
)
.payload(payload)
.send()?;

Ok(())
}
24 changes: 23 additions & 1 deletion src/kernel_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ pub enum VfsAction {
RemoveDir,
RemoveDirAll,
Rename(String),
Metadata,
AddZip,
Len,
SetLen(u64),
Expand All @@ -219,13 +220,34 @@ pub enum SeekFrom {
Current(i64),
}

#[derive(Debug, Serialize, Deserialize)]
pub enum FileType {
File,
Directory,
Symlink,
Other,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct FileMetadata {
pub file_type: FileType,
pub len: u64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DirEntry {
pub path: String,
pub file_type: FileType,
}

#[derive(Debug, Serialize, Deserialize)]
pub enum VfsResponse {
Ok,
Err(VfsError),
Read,
ReadDir(Vec<String>),
ReadDir(Vec<DirEntry>),
ReadToString(String),
Metadata(FileMetadata),
Len(u64),
Hash([u8; 32]),
}
Expand Down

0 comments on commit fe0de63

Please sign in to comment.