Skip to main content

freya_components/
http.rs

1//! HTTP requests with the `remote-asset` feature enabled.
2//!
3//! ```rust,no_run
4//! use freya_components::{
5//!     Url,
6//!     http,
7//! };
8//!
9//! # async fn load() -> anyhow::Result<()> {
10//! let url = Url::parse("https://example.com")?;
11//! let bytes = http::fetch(url).await?;
12//! # Ok(())
13//! # }
14//! ```
15//!
16//! Native requests use a default client unless you provide one at launch:
17//!
18//! ```rust,no_run
19//! # use freya::prelude::*;
20//! # fn app() -> impl IntoElement { "Hello" }
21//! fn main() -> Result<(), reqwest::Error> {
22//!     let client = reqwest::blocking::Client::builder()
23//!         .user_agent("MyApp/1.0")
24//!         .build()?;
25//!     launch(
26//!         LaunchConfig::new()
27//!             .with_global_context(client)
28//!             .with_window(WindowConfig::new(app)),
29//!     );
30//!     Ok(())
31//! }
32//! ```
33//!
34//! On web, requests use the browser and follow its CORS rules.
35
36use bytes::Bytes;
37use url::Url;
38
39/// Fetches `url` using the app-wide HTTP client on native platforms.
40#[cfg(not(target_os = "emscripten"))]
41pub async fn fetch(url: Url) -> anyhow::Result<Bytes> {
42    let client: reqwest::blocking::Client = freya_core::prelude::GlobalContexts::get()
43        .get_context_or_insert(reqwest::blocking::Client::new);
44    freya_core::prelude::thread(move || Ok(client.get(url).send()?.error_for_status()?.bytes()?))
45        .await
46}
47
48/// Fetches `url` using the browser's HTTP client on web.
49///
50/// Browser CORS rules apply. Returns an error if the request fails.
51#[cfg(target_os = "emscripten")]
52pub async fn fetch(url: Url) -> anyhow::Result<Bytes> {
53    use std::ffi::{
54        CString,
55        c_char,
56        c_int,
57        c_void,
58    };
59
60    use anyhow::Context;
61    use futures_channel::oneshot::{
62        Sender,
63        channel,
64    };
65
66    unsafe extern "C" {
67        fn emscripten_async_wget_data(
68            url: *const c_char,
69            argument: *mut c_void,
70            on_load: extern "C" fn(*mut c_void, *mut c_void, c_int),
71            on_error: extern "C" fn(*mut c_void),
72        );
73    }
74
75    extern "C" fn on_load(argument: *mut c_void, data: *mut c_void, size: c_int) {
76        let sender = unsafe { Box::from_raw(argument.cast::<Sender<Option<Bytes>>>()) };
77        let data = unsafe { std::slice::from_raw_parts(data.cast::<u8>(), size as usize) };
78        sender.send(Some(Bytes::copy_from_slice(data))).ok();
79    }
80
81    extern "C" fn on_error(argument: *mut c_void) {
82        let sender = unsafe { Box::from_raw(argument.cast::<Sender<Option<Bytes>>>()) };
83        sender.send(None).ok();
84    }
85
86    let c_url = CString::new(url.as_str())?;
87    let (sender, receiver) = channel::<Option<Bytes>>();
88    let argument = Box::into_raw(Box::new(sender)).cast();
89
90    unsafe { emscripten_async_wget_data(c_url.as_ptr(), argument, on_load, on_error) };
91
92    receiver
93        .await
94        .ok()
95        .flatten()
96        .with_context(|| format!("Failed to fetch {url}."))
97}