diff --git a/packages/net/Cargo.toml b/packages/net/Cargo.toml index bdcad152..ac821efc 100644 --- a/packages/net/Cargo.toml +++ b/packages/net/Cargo.toml @@ -9,3 +9,14 @@ tokio = { workspace = true } reqwest = { version = "0.12.7" } data-url = "0.3.1" thiserror = "1.0.63" +[package] +name = "blitz-net" +version = "0.1.0" +edition = "2021" + +[dependencies] +blitz-traits = { path = "../traits" } +tokio = { workspace = true } +reqwest = { version = "0.12.7" } +data-url = "0.3.1" +thiserror = "1.0.63" diff --git a/packages/net/src/lib.rs b/packages/net/src/lib.rs index 1fb6fc75..6bd020ba 100644 --- a/packages/net/src/lib.rs +++ b/packages/net/src/lib.rs @@ -92,3 +92,97 @@ impl Callback for MpscCallback { let _ = self.0.send(data); } } +use blitz_traits::net::{BoxedHandler, Callback, NetProvider, SharedCallback, Url}; +use data_url::DataUrl; +use reqwest::Client; +use std::sync::Arc; +use thiserror::Error; +use tokio::runtime::Handle; + +const USER_AGENT: &str = "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"; + +pub struct Provider { + rt: Handle, + client: Client, + callback: SharedCallback, +} +impl Provider { + pub fn new(rt_handle: Handle, callback: SharedCallback) -> Self { + Self { + rt: rt_handle, + client: Client::new(), + callback, + } + } +} +impl Provider { + pub fn is_empty(&self) -> bool { + Arc::strong_count(&self.callback) == 1 + } + async fn fetch_inner( + client: Client, + url: Url, + callback: SharedCallback, + handler: BoxedHandler, + ) -> Result<(), ProviderError> { + match url.scheme() { + "data" => { + let data_url = DataUrl::process(url.as_str())?; + let decoded = data_url.decode_to_vec()?; + handler.bytes(&decoded.0, callback); + } + "file" => { + let file_content = std::fs::read(url.path())?; + handler.bytes(&file_content, callback); + } + _ => { + let response = client + .request(handler.method(), url) + .header("User-Agent", USER_AGENT) + .send() + .await?; + handler.bytes(&response.bytes().await?, callback); + } + } + Ok(()) + } +} + +impl NetProvider for Provider { + type Data = D; + fn fetch(&self, url: Url, handler: BoxedHandler) { + let client = self.client.clone(); + let callback = Arc::clone(&self.callback); + drop( + self.rt + .spawn(Self::fetch_inner(client, url, callback, handler)), + ); + } +} + +#[derive(Error, Debug)] +enum ProviderError { + #[error("{0}")] + Io(#[from] std::io::Error), + #[error("{0}")] + DataUrl(#[from] data_url::DataUrlError), + #[error("{0}")] + DataUrlBas64(#[from] data_url::forgiving_base64::InvalidBase64), + #[error("{0}")] + ReqwestError(#[from] reqwest::Error), +} + +use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; +pub struct MpscCallback(UnboundedSender); +impl MpscCallback { + pub fn new() -> (UnboundedReceiver, Self) { + let (send, recv) = unbounded_channel(); + (recv, Self(send)) + } +} +impl Callback for MpscCallback { + type Data = T; + fn call(self: Arc, data: Self::Data) { + let _ = self.0.send(data); + } +} diff --git a/packages/traits/Cargo.toml b/packages/traits/Cargo.toml index 9495bbde..27372a4b 100644 --- a/packages/traits/Cargo.toml +++ b/packages/traits/Cargo.toml @@ -3,6 +3,13 @@ name = "blitz-traits" version = "0.1.0" edition = "2021" +[dependencies] +http = "1.1.0" +url = "2.5.2"[package] +name = "blitz-traits" +version = "0.1.0" +edition = "2021" + [dependencies] http = "1.1.0" url = "2.5.2" \ No newline at end of file diff --git a/packages/traits/src/lib.rs b/packages/traits/src/lib.rs index f9faf2ff..385e79df 100644 --- a/packages/traits/src/lib.rs +++ b/packages/traits/src/lib.rs @@ -1 +1,2 @@ pub mod net; +pub mod net; diff --git a/packages/traits/src/net.rs b/packages/traits/src/net.rs index c3fe711b..60d81d80 100644 --- a/packages/traits/src/net.rs +++ b/packages/traits/src/net.rs @@ -40,3 +40,45 @@ impl Callback for DummyCallback { type Data = T; fn call(self: Arc, _data: Self::Data) {} } +pub use http::Method; +use std::marker::PhantomData; +use std::sync::Arc; +pub use url::Url; + +pub type BoxedHandler = Box>; +pub type SharedCallback = Arc>; +pub type SharedProvider = Arc>; + +pub trait NetProvider { + type Data; + fn fetch(&self, url: Url, handler: BoxedHandler); +} + +pub trait RequestHandler: Send + Sync + 'static { + type Data; + fn bytes(self: Box, bytes: &[u8], callback: SharedCallback); + fn method(&self) -> Method { + Method::GET + } +} + +pub trait Callback: Send + Sync + 'static { + type Data; + fn call(self: Arc, data: Self::Data); +} + +pub struct DummyProvider(PhantomData); +impl Default for DummyProvider { + fn default() -> Self { + Self(PhantomData) + } +} +impl NetProvider for DummyProvider { + type Data = D; + fn fetch(&self, _url: Url, _handler: BoxedHandler) {} +} +pub struct DummyCallback(PhantomData); +impl Callback for DummyCallback { + type Data = T; + fn call(self: Arc, _data: Self::Data) {} +}