From 0eeb08c45bf93a54fb841490ee40ba43273fdd64 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Wed, 13 Nov 2024 12:53:25 +1100 Subject: [PATCH 01/36] feat: http_source initial commit --- Cargo.toml | 11 ++ crates/bevy_asset/Cargo.toml | 7 + crates/bevy_asset/src/http_source.rs | 255 +++++++++++++++++++++++++++ crates/bevy_asset/src/lib.rs | 6 + examples/asset/http_asset.rs | 18 ++ 5 files changed, 297 insertions(+) create mode 100644 crates/bevy_asset/src/http_source.rs create mode 100644 examples/asset/http_asset.rs diff --git a/Cargo.toml b/Cargo.toml index 307558e0bc7aa..284447fb9e439 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1632,6 +1632,17 @@ category = "Assets" # Uses non-standard asset path wasm = false +[[example]] +name = "http_asset" +path = "examples/asset/http_asset.rs" +doc-scrape-examples = true + +[package.metadata.example.http_asset] +name = "HTTP Asset" +description = "Load an asset from a http source" +category = "Assets" +wasm = true + [[example]] name = "hot_asset_reloading" path = "examples/asset/hot_asset_reloading.rs" diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index f596805b46da0..71f9982bb62cb 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -11,9 +11,11 @@ keywords = ["bevy"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] +default = ["http_source"] file_watcher = ["notify-debouncer-full", "watch"] embedded_watcher = ["file_watcher"] multi_threaded = ["bevy_tasks/multi_threaded"] +http_source = ["pin-project", "surf"] asset_processor = [] watch = [] trace = [] @@ -51,6 +53,8 @@ derive_more = { version = "1", default-features = false, features = [ ] } uuid = { version = "1.0", features = ["v4"] } +pin-project = { version = "1", optional = true } + [target.'cfg(target_os = "android")'.dependencies] bevy_window = { path = "../bevy_window", version = "0.15.0-dev" } @@ -66,6 +70,9 @@ js-sys = "0.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] notify-debouncer-full = { version = "0.4.0", optional = true } +surf = { version = "2.3", default-features = false, features = [ + "h1-client-rustls", +], optional = true } [dev-dependencies] bevy_core = { path = "../bevy_core", version = "0.15.0-dev" } diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs new file mode 100644 index 0000000000000..815ad7558290a --- /dev/null +++ b/crates/bevy_asset/src/http_source.rs @@ -0,0 +1,255 @@ +use crate::io::{AssetReader, AssetReaderError, Reader}; +use crate::io::{AssetSource, PathStream}; +use crate::AssetApp; +use bevy_app::App; +use bevy_utils::ConditionalSendFuture; +use std::path::{Path, PathBuf}; + +/// +/// Adds the `http` and `https` asset sources to the app. +/// Any asset path that begins with `http://` or `https://` will be loaded from the web +/// instead of . +pub fn http_source_plugin(app: &mut App) { + app.register_asset_source( + "http", + AssetSource::build().with_reader(|| Box::new(WebAssetReader::Http)), + ); + app.register_asset_source( + "https", + AssetSource::build().with_reader(|| Box::new(WebAssetReader::Https)), + ); +} + +/// Treats paths as urls to load assets from. +pub enum WebAssetReader { + /// Unencrypted connections. + Http, + /// Use TLS for setting up connections. + Https, +} + +impl WebAssetReader { + fn make_uri(&self, path: &Path) -> PathBuf { + PathBuf::from(match self { + Self::Http => "http://", + Self::Https => "https://", + }) + .join(path) + } + + /// See [bevy::asset::io::get_meta_path] + fn make_meta_uri(&self, path: &Path) -> Option { + let mut uri = self.make_uri(path); + let mut extension = path.extension()?.to_os_string(); + extension.push(".meta"); + uri.set_extension(extension); + Some(uri) + } +} + +#[cfg(target_arch = "wasm32")] +async fn get<'a>(path: PathBuf) -> Result>, AssetReaderError> { + use bevy::asset::io::VecReader; + use js_sys::Uint8Array; + use wasm_bindgen::JsCast; + use wasm_bindgen_futures::JsFuture; + use web_sys::Response; + + fn js_value_to_err<'a>( + context: &'a str, + ) -> impl FnOnce(wasm_bindgen::JsValue) -> std::io::Error + 'a { + move |value| { + let message = match js_sys::JSON::stringify(&value) { + Ok(js_str) => format!("Failed to {context}: {js_str}"), + Err(_) => { + format!( + "Failed to {context} and also failed to stringify the JSValue of the error" + ) + } + }; + + std::io::Error::new(std::io::ErrorKind::Other, message) + } + } + + let window = web_sys::window().unwrap(); + let resp_value = JsFuture::from(window.fetch_with_str(path.to_str().unwrap())) + .await + .map_err(js_value_to_err("fetch path"))?; + let resp = resp_value + .dyn_into::() + .map_err(js_value_to_err("convert fetch to Response"))?; + match resp.status() { + 200 => { + let data = JsFuture::from(resp.array_buffer().unwrap()).await.unwrap(); + let bytes = Uint8Array::new(&data).to_vec(); + let reader: Box = Box::new(VecReader::new(bytes)); + Ok(reader) + } + 404 => Err(AssetReaderError::NotFound(path)), + status => Err(AssetReaderError::Io( + std::io::Error::new( + std::io::ErrorKind::Other, + format!("Encountered unexpected HTTP status {status}"), + ) + .into(), + )), + } +} + +#[cfg(not(target_arch = "wasm32"))] +async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { + use std::future::Future; + use std::io; + use std::pin::Pin; + use std::task::{Context, Poll}; + + use crate::io::VecReader; + use surf::StatusCode; + + #[pin_project::pin_project] + struct ContinuousPoll(#[pin] T); + + impl Future for ContinuousPoll { + type Output = T::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Always wake - blocks on single threaded executor. + cx.waker().wake_by_ref(); + + self.project().0.poll(cx) + } + } + + let str_path = path.to_str().ok_or_else(|| { + AssetReaderError::Io( + io::Error::new( + io::ErrorKind::Other, + format!("non-utf8 path: {}", path.display()), + ) + .into(), + ) + })?; + let mut response = ContinuousPoll(surf::get(str_path)).await.map_err(|err| { + AssetReaderError::Io( + io::Error::new( + io::ErrorKind::Other, + format!( + "unexpected status code {} while loading {}: {}", + err.status(), + path.display(), + err.into_inner(), + ), + ) + .into(), + ) + })?; + + match response.status() { + StatusCode::Ok => Ok(Box::new(VecReader::new( + ContinuousPoll(response.body_bytes()) + .await + .map_err(|_| AssetReaderError::NotFound(path.to_path_buf()))?, + )) as _), + StatusCode::NotFound => Err(AssetReaderError::NotFound(path)), + code => Err(AssetReaderError::Io( + io::Error::new( + io::ErrorKind::Other, + format!( + "unexpected status code {} while loading {}", + code, + path.display() + ), + ) + .into(), + )), + } +} + +impl AssetReader for WebAssetReader { + fn read<'a>( + &'a self, + path: &'a Path, + ) -> impl ConditionalSendFuture, AssetReaderError>> { + get(self.make_uri(path)) + } + + async fn read_meta<'a>(&'a self, path: &'a Path) -> Result, AssetReaderError> { + match self.make_meta_uri(path) { + Some(uri) => get(uri).await, + None => Err(AssetReaderError::NotFound( + "source path has no extension".into(), + )), + } + } + + async fn is_directory<'a>(&'a self, _path: &'a Path) -> Result { + Ok(false) + } + + async fn read_directory<'a>( + &'a self, + path: &'a Path, + ) -> Result, AssetReaderError> { + Err(AssetReaderError::NotFound(self.make_uri(path))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn make_http_uri() { + assert_eq!( + WebAssetReader::Http + .make_uri(Path::new("s3.johanhelsing.studio/dump/favicon.png")) + .to_str() + .unwrap(), + "http://s3.johanhelsing.studio/dump/favicon.png" + ); + } + + #[test] + fn make_https_uri() { + assert_eq!( + WebAssetReader::Https + .make_uri(Path::new("s3.johanhelsing.studio/dump/favicon.png")) + .to_str() + .unwrap(), + "https://s3.johanhelsing.studio/dump/favicon.png" + ); + } + + #[test] + fn make_http_meta_uri() { + assert_eq!( + WebAssetReader::Http + .make_meta_uri(Path::new("s3.johanhelsing.studio/dump/favicon.png")) + .expect("cannot create meta uri") + .to_str() + .unwrap(), + "http://s3.johanhelsing.studio/dump/favicon.png.meta" + ); + } + + #[test] + fn make_https_meta_uri() { + assert_eq!( + WebAssetReader::Https + .make_meta_uri(Path::new("s3.johanhelsing.studio/dump/favicon.png")) + .expect("cannot create meta uri") + .to_str() + .unwrap(), + "https://s3.johanhelsing.studio/dump/favicon.png.meta" + ); + } + + #[test] + fn make_https_without_extension_meta_uri() { + assert_eq!( + WebAssetReader::Https.make_meta_uri(Path::new("s3.johanhelsing.studio/dump/favicon")), + None + ); + } +} diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 8937c8838c171..e3183580300dc 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -179,6 +179,9 @@ mod reflect; mod render_asset; mod server; +#[cfg(feature = "http_source")] +mod http_source; + pub use assets::*; pub use bevy_asset_macros::Asset; pub use direct_access_ext::DirectAssetAccessExt; @@ -310,6 +313,9 @@ impl AssetPlugin { impl Plugin for AssetPlugin { fn build(&self, app: &mut App) { + #[cfg(feature = "http_source")] + app.add_plugins(http_source::http_source_plugin); + let embedded = EmbeddedAssetRegistry::default(); { let mut sources = app diff --git a/examples/asset/http_asset.rs b/examples/asset/http_asset.rs new file mode 100644 index 0000000000000..f89403d545434 --- /dev/null +++ b/examples/asset/http_asset.rs @@ -0,0 +1,18 @@ +//! Example usage of the `http` asset source to load assets from the web. +use bevy::prelude::*; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .run(); +} + +fn setup(mut commands: Commands, asset_server: Res) { + commands.spawn(Camera2d::default()); + + commands.spawn( + // Simply use a url where you would normally use an asset folder relative path + Sprite::from_image(asset_server.load("https://raw.githubusercontent.com/bevyengine/bevy/refs/heads/main/assets/branding/bevy_bird_dark.png")) + ); +} From c0d3b3cc0912f5d724938c12df9ce7e7c9d53229 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Wed, 13 Nov 2024 13:09:22 +1100 Subject: [PATCH 02/36] docs: update README --- examples/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/README.md b/examples/README.md index 92bf3f188be1b..5cb37b4d7361e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -240,6 +240,7 @@ Example | Description [Custom Asset IO](../examples/asset/custom_asset_reader.rs) | Implements a custom AssetReader [Embedded Asset](../examples/asset/embedded_asset.rs) | Embed an asset in the application binary and load it [Extra asset source](../examples/asset/extra_source.rs) | Load an asset from a non-standard asset source +[HTTP Asset](../examples/asset/http_asset.rs) | Load an asset from a http source [Hot Reloading of Assets](../examples/asset/hot_asset_reloading.rs) | Demonstrates automatic reloading of assets when modified on disk [Mult-asset synchronization](../examples/asset/multi_asset_sync.rs) | Demonstrates how to wait for multiple assets to be loaded. [Repeated texture configuration](../examples/asset/repeated_texture.rs) | How to configure the texture to repeat instead of the default clamp to edges From f4cdc63a808f2f64b4f9c85db15833ae8a08d654 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Wed, 13 Nov 2024 13:24:49 +1100 Subject: [PATCH 03/36] feat: http_cache_surf --- .gitignore | 2 ++ crates/bevy_asset/Cargo.toml | 3 +- crates/bevy_asset/src/http_source.rs | 50 +++++++++++++++++----------- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index d3b84d9590bb8..2a73629c590cf 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,8 @@ dxil.dll assets/**/*.meta crates/bevy_asset/imported_assets imported_assets +# Bevy Asset - http-cache-surf +http-cacache # Bevy Examples example_showcase_config.ron diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index 71f9982bb62cb..37c5115611ce3 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -15,7 +15,7 @@ default = ["http_source"] file_watcher = ["notify-debouncer-full", "watch"] embedded_watcher = ["file_watcher"] multi_threaded = ["bevy_tasks/multi_threaded"] -http_source = ["pin-project", "surf"] +http_source = ["pin-project", "surf", "http-cache-surf"] asset_processor = [] watch = [] trace = [] @@ -73,6 +73,7 @@ notify-debouncer-full = { version = "0.4.0", optional = true } surf = { version = "2.3", default-features = false, features = [ "h1-client-rustls", ], optional = true } +http-cache-surf = { version = "0.13", optional = true } [dev-dependencies] bevy_core = { path = "../bevy_core", version = "0.15.0-dev" } diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index 815ad7558290a..c71928d25d9e3 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -5,10 +5,9 @@ use bevy_app::App; use bevy_utils::ConditionalSendFuture; use std::path::{Path, PathBuf}; -/// /// Adds the `http` and `https` asset sources to the app. -/// Any asset path that begins with `http://` or `https://` will be loaded from the web -/// instead of . +/// Any asset path that begins with `http` or `https` will be loaded from the web +/// via `fetch`(wasm) or `surf`(native). pub fn http_source_plugin(app: &mut App) { app.register_asset_source( "http", @@ -37,7 +36,7 @@ impl WebAssetReader { .join(path) } - /// See [bevy::asset::io::get_meta_path] + /// See [crate::io::get_meta_path] fn make_meta_uri(&self, path: &Path) -> Option { let mut uri = self.make_uri(path); let mut extension = path.extension()?.to_os_string(); @@ -99,12 +98,13 @@ async fn get<'a>(path: PathBuf) -> Result>, AssetReaderError> { #[cfg(not(target_arch = "wasm32"))] async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { - use std::future::Future; + use core::future::Future; + use core::pin::Pin; + use core::task::{Context, Poll}; use std::io; - use std::pin::Pin; - use std::task::{Context, Poll}; use crate::io::VecReader; + use http_cache_surf::{CACacheManager, Cache, CacheMode, HttpCache, HttpCacheOptions}; use surf::StatusCode; #[pin_project::pin_project] @@ -130,20 +130,30 @@ async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { .into(), ) })?; - let mut response = ContinuousPoll(surf::get(str_path)).await.map_err(|err| { - AssetReaderError::Io( - io::Error::new( - io::ErrorKind::Other, - format!( - "unexpected status code {} while loading {}: {}", - err.status(), - path.display(), - err.into_inner(), - ), + + let req = surf::get(str_path); + let middleware_client = surf::client().with(Cache(HttpCache { + mode: CacheMode::Default, + manager: CACacheManager::default(), + options: HttpCacheOptions::default(), + })); + + let mut response = ContinuousPoll(middleware_client.send(req)) + .await + .map_err(|err| { + AssetReaderError::Io( + io::Error::new( + io::ErrorKind::Other, + format!( + "unexpected status code {} while loading {}: {}", + err.status(), + path.display(), + err.into_inner(), + ), + ) + .into(), ) - .into(), - ) - })?; + })?; match response.status() { StatusCode::Ok => Ok(Box::new(VecReader::new( From 66046b9259cf4eab5c507889bb5935f679e3ca1a Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Wed, 13 Nov 2024 13:30:03 +1100 Subject: [PATCH 04/36] docs: add backticks --- crates/bevy_asset/src/http_source.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index c71928d25d9e3..6a44cf5a2f015 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -36,7 +36,7 @@ impl WebAssetReader { .join(path) } - /// See [crate::io::get_meta_path] + /// See [`crate::io::get_meta_path`] fn make_meta_uri(&self, path: &Path) -> Option { let mut uri = self.make_uri(path); let mut extension = path.extension()?.to_os_string(); From 9160a1d28d7128df8fccd64b0dedf09acbbcdd6c Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Wed, 13 Nov 2024 14:27:44 +1100 Subject: [PATCH 05/36] patch --- examples/asset/http_asset.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/asset/http_asset.rs b/examples/asset/http_asset.rs index f89403d545434..27ee70099b449 100644 --- a/examples/asset/http_asset.rs +++ b/examples/asset/http_asset.rs @@ -9,7 +9,7 @@ fn main() { } fn setup(mut commands: Commands, asset_server: Res) { - commands.spawn(Camera2d::default()); + commands.spawn(Camera2d); commands.spawn( // Simply use a url where you would normally use an asset folder relative path From 35b943cc2e771791de283c12027eed0477d2f07f Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Wed, 13 Nov 2024 14:30:02 +1100 Subject: [PATCH 06/36] fix: wasm build --- crates/bevy_asset/src/http_source.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index 6a44cf5a2f015..3655fdc196067 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -47,7 +47,7 @@ impl WebAssetReader { } #[cfg(target_arch = "wasm32")] -async fn get<'a>(path: PathBuf) -> Result>, AssetReaderError> { +async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { use bevy::asset::io::VecReader; use js_sys::Uint8Array; use wasm_bindgen::JsCast; @@ -82,7 +82,7 @@ async fn get<'a>(path: PathBuf) -> Result>, AssetReaderError> { 200 => { let data = JsFuture::from(resp.array_buffer().unwrap()).await.unwrap(); let bytes = Uint8Array::new(&data).to_vec(); - let reader: Box = Box::new(VecReader::new(bytes)); + let reader = Box::new(VecReader::new(bytes)); Ok(reader) } 404 => Err(AssetReaderError::NotFound(path)), From 4c2702df1b2e89648351c9cdf3e21fcae192c2e1 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Wed, 13 Nov 2024 14:47:39 +1100 Subject: [PATCH 07/36] fix: wasm build --- crates/bevy_asset/src/http_source.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index 3655fdc196067..c4964fc180870 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -48,7 +48,7 @@ impl WebAssetReader { #[cfg(target_arch = "wasm32")] async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { - use bevy::asset::io::VecReader; + use crate::io::VecReader; use js_sys::Uint8Array; use wasm_bindgen::JsCast; use wasm_bindgen_futures::JsFuture; From 91385f11affd72e36840b0aae5c4b1992b911ec5 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Mon, 18 Nov 2024 07:06:36 +0000 Subject: [PATCH 08/36] replace `surf` with `ureq` --- crates/bevy_asset/Cargo.toml | 9 ++---- crates/bevy_asset/src/http_source.rs | 47 ++++++---------------------- 2 files changed, 11 insertions(+), 45 deletions(-) diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index 37c5115611ce3..78875a6d1eecc 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -15,7 +15,7 @@ default = ["http_source"] file_watcher = ["notify-debouncer-full", "watch"] embedded_watcher = ["file_watcher"] multi_threaded = ["bevy_tasks/multi_threaded"] -http_source = ["pin-project", "surf", "http-cache-surf"] +http_source = ["ureq"] asset_processor = [] watch = [] trace = [] @@ -53,8 +53,6 @@ derive_more = { version = "1", default-features = false, features = [ ] } uuid = { version = "1.0", features = ["v4"] } -pin-project = { version = "1", optional = true } - [target.'cfg(target_os = "android")'.dependencies] bevy_window = { path = "../bevy_window", version = "0.15.0-dev" } @@ -70,10 +68,7 @@ js-sys = "0.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] notify-debouncer-full = { version = "0.4.0", optional = true } -surf = { version = "2.3", default-features = false, features = [ - "h1-client-rustls", -], optional = true } -http-cache-surf = { version = "0.13", optional = true } +ureq = { version = "2.10", optional = true } [dev-dependencies] bevy_core = { path = "../bevy_core", version = "0.15.0-dev" } diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index c4964fc180870..fbef9b6a25638 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -98,28 +98,8 @@ async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { #[cfg(not(target_arch = "wasm32"))] async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { - use core::future::Future; - use core::pin::Pin; - use core::task::{Context, Poll}; use std::io; - use crate::io::VecReader; - use http_cache_surf::{CACacheManager, Cache, CacheMode, HttpCache, HttpCacheOptions}; - use surf::StatusCode; - - #[pin_project::pin_project] - struct ContinuousPoll(#[pin] T); - - impl Future for ContinuousPoll { - type Output = T::Output; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - // Always wake - blocks on single threaded executor. - cx.waker().wake_by_ref(); - - self.project().0.poll(cx) - } - } let str_path = path.to_str().ok_or_else(|| { AssetReaderError::Io( @@ -131,24 +111,15 @@ async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { ) })?; - let req = surf::get(str_path); - let middleware_client = surf::client().with(Cache(HttpCache { - mode: CacheMode::Default, - manager: CACacheManager::default(), - options: HttpCacheOptions::default(), - })); - - let mut response = ContinuousPoll(middleware_client.send(req)) - .await + let response = ureq::get(str_path).call() .map_err(|err| { AssetReaderError::Io( io::Error::new( io::ErrorKind::Other, format!( - "unexpected status code {} while loading {}: {}", - err.status(), + "unexpected error while loading {}: {}", path.display(), - err.into_inner(), + err, ), ) .into(), @@ -156,12 +127,12 @@ async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { })?; match response.status() { - StatusCode::Ok => Ok(Box::new(VecReader::new( - ContinuousPoll(response.body_bytes()) - .await - .map_err(|_| AssetReaderError::NotFound(path.to_path_buf()))?, - )) as _), - StatusCode::NotFound => Err(AssetReaderError::NotFound(path)), + 200 => { + let mut reader = response.into_reader(); + let mut buffer = Vec::new(); + reader.read_to_end(&mut buffer)?; + Ok(Box::new(VecReader::new(buffer)))}, + 404 => Err(AssetReaderError::NotFound(path)), code => Err(AssetReaderError::Io( io::Error::new( io::ErrorKind::Other, From 71d8fb2a9a19e3972d9fd8179cc1bfe26c7e1c06 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Mon, 18 Nov 2024 07:10:18 +0000 Subject: [PATCH 09/36] cargo format --- crates/bevy_asset/src/http_source.rs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index fbef9b6a25638..db02de585e593 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -98,8 +98,8 @@ async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { #[cfg(not(target_arch = "wasm32"))] async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { - use std::io; use crate::io::VecReader; + use std::io; let str_path = path.to_str().ok_or_else(|| { AssetReaderError::Io( @@ -111,27 +111,23 @@ async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { ) })?; - let response = ureq::get(str_path).call() - .map_err(|err| { - AssetReaderError::Io( - io::Error::new( - io::ErrorKind::Other, - format!( - "unexpected error while loading {}: {}", - path.display(), - err, - ), - ) - .into(), + let response = ureq::get(str_path).call().map_err(|err| { + AssetReaderError::Io( + io::Error::new( + io::ErrorKind::Other, + format!("unexpected error while loading {}: {}", path.display(), err,), ) - })?; + .into(), + ) + })?; match response.status() { 200 => { let mut reader = response.into_reader(); let mut buffer = Vec::new(); reader.read_to_end(&mut buffer)?; - Ok(Box::new(VecReader::new(buffer)))}, + Ok(Box::new(VecReader::new(buffer))) + } 404 => Err(AssetReaderError::NotFound(path)), code => Err(AssetReaderError::Io( io::Error::new( From 7e9611f8f9da567b9f4eafcc14b00420de50fef1 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Mon, 18 Nov 2024 07:53:21 +0000 Subject: [PATCH 10/36] rename http_source example --- Cargo.toml | 4 ++-- examples/asset/{http_asset.rs => http_source.rs} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename examples/asset/{http_asset.rs => http_source.rs} (100%) diff --git a/Cargo.toml b/Cargo.toml index b6fabad7f5cca..1689c6329b749 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1637,8 +1637,8 @@ category = "Assets" wasm = false [[example]] -name = "http_asset" -path = "examples/asset/http_asset.rs" +name = "http_source" +path = "examples/asset/http_source.rs" doc-scrape-examples = true [package.metadata.example.http_asset] diff --git a/examples/asset/http_asset.rs b/examples/asset/http_source.rs similarity index 100% rename from examples/asset/http_asset.rs rename to examples/asset/http_source.rs From 7ca96f2a5381c65f0853541652c7b361338e332d Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Fri, 6 Dec 2024 12:05:42 +1100 Subject: [PATCH 11/36] fix: asset source metadata --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1689c6329b749..bd37da54cb80d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1630,7 +1630,7 @@ path = "examples/asset/extra_source.rs" doc-scrape-examples = true [package.metadata.example.extra_asset_source] -name = "Extra asset source" +name = "Extra Asset Source" description = "Load an asset from a non-standard asset source" category = "Assets" # Uses non-standard asset path @@ -1641,8 +1641,8 @@ name = "http_source" path = "examples/asset/http_source.rs" doc-scrape-examples = true -[package.metadata.example.http_asset] -name = "HTTP Asset" +[package.metadata.example.http_source] +name = "HTTP Asset Source" description = "Load an asset from a http source" category = "Assets" wasm = true From 9aa844e695f47993204883c47d79ef0b0ef5da25 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Fri, 6 Dec 2024 12:28:10 +1100 Subject: [PATCH 12/36] fix: map ureq 404 to AssetReadError::NotFound --- crates/bevy_asset/src/http_source.rs | 41 ++++++++++++++++------------ 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index db02de585e593..4c248de1b11dc 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -111,31 +111,38 @@ async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { ) })?; - let response = ureq::get(str_path).call().map_err(|err| { - AssetReaderError::Io( - io::Error::new( - io::ErrorKind::Other, - format!("unexpected error while loading {}: {}", path.display(), err,), - ) - .into(), - ) - })?; - - match response.status() { - 200 => { + match ureq::get(str_path).call() { + Ok(response) => { let mut reader = response.into_reader(); let mut buffer = Vec::new(); reader.read_to_end(&mut buffer)?; Ok(Box::new(VecReader::new(buffer))) } - 404 => Err(AssetReaderError::NotFound(path)), - code => Err(AssetReaderError::Io( + // ureq considers all >=400 status codes as errors + Err(ureq::Error::Status(code, _response)) => { + if code == 404 { + Err(AssetReaderError::NotFound(path)) + } else { + Err(AssetReaderError::Io( + io::Error::new( + io::ErrorKind::Other, + format!( + "unexpected status code {} while loading {}", + code, + path.display() + ), + ) + .into(), + )) + } + } + Err(ureq::Error::Transport(err)) => Err(AssetReaderError::Io( io::Error::new( io::ErrorKind::Other, format!( - "unexpected status code {} while loading {}", - code, - path.display() + "unexpected error while loading asset {}: {}", + path.display(), + err ), ) .into(), From ad69a6de9cf5fcec7fdc2e6d5f3185c7be13f456 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Fri, 6 Dec 2024 12:34:19 +1100 Subject: [PATCH 13/36] rename: WebAsseetReader > HttpSourceAssetReader --- crates/bevy_asset/src/http_source.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index 4c248de1b11dc..9c49348b35340 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -11,23 +11,25 @@ use std::path::{Path, PathBuf}; pub fn http_source_plugin(app: &mut App) { app.register_asset_source( "http", - AssetSource::build().with_reader(|| Box::new(WebAssetReader::Http)), + AssetSource::build().with_reader(|| Box::new(HttpSourceAssetReader::Http)), ); app.register_asset_source( "https", - AssetSource::build().with_reader(|| Box::new(WebAssetReader::Https)), + AssetSource::build().with_reader(|| Box::new(HttpSourceAssetReader::Https)), ); } -/// Treats paths as urls to load assets from. -pub enum WebAssetReader { +/// Asset reader that treats paths as urls to load assets from. +/// This should not be confused with the [`HttpWasmAssetReader`] which is loads +/// *local* assets for wasm bevy apps. +pub enum HttpSourceAssetReader { /// Unencrypted connections. Http, /// Use TLS for setting up connections. Https, } -impl WebAssetReader { +impl HttpSourceAssetReader { fn make_uri(&self, path: &Path) -> PathBuf { PathBuf::from(match self { Self::Http => "http://", @@ -150,7 +152,7 @@ async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { } } -impl AssetReader for WebAssetReader { +impl AssetReader for HttpSourceAssetReader { fn read<'a>( &'a self, path: &'a Path, @@ -186,7 +188,7 @@ mod tests { #[test] fn make_http_uri() { assert_eq!( - WebAssetReader::Http + HttpSourceAssetReader::Http .make_uri(Path::new("s3.johanhelsing.studio/dump/favicon.png")) .to_str() .unwrap(), @@ -197,7 +199,7 @@ mod tests { #[test] fn make_https_uri() { assert_eq!( - WebAssetReader::Https + HttpSourceAssetReader::Https .make_uri(Path::new("s3.johanhelsing.studio/dump/favicon.png")) .to_str() .unwrap(), @@ -208,7 +210,7 @@ mod tests { #[test] fn make_http_meta_uri() { assert_eq!( - WebAssetReader::Http + HttpSourceAssetReader::Http .make_meta_uri(Path::new("s3.johanhelsing.studio/dump/favicon.png")) .expect("cannot create meta uri") .to_str() @@ -220,7 +222,7 @@ mod tests { #[test] fn make_https_meta_uri() { assert_eq!( - WebAssetReader::Https + HttpSourceAssetReader::Https .make_meta_uri(Path::new("s3.johanhelsing.studio/dump/favicon.png")) .expect("cannot create meta uri") .to_str() @@ -232,7 +234,7 @@ mod tests { #[test] fn make_https_without_extension_meta_uri() { assert_eq!( - WebAssetReader::Https.make_meta_uri(Path::new("s3.johanhelsing.studio/dump/favicon")), + HttpSourceAssetReader::Https.make_meta_uri(Path::new("s3.johanhelsing.studio/dump/favicon")), None ); } From a031226ca6b799b1ea407324c88f2c00876fbad5 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Fri, 6 Dec 2024 12:35:13 +1100 Subject: [PATCH 14/36] build templated page --- examples/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/README.md b/examples/README.md index 5cb37b4d7361e..95a41d8615718 100644 --- a/examples/README.md +++ b/examples/README.md @@ -239,8 +239,8 @@ Example | Description [Custom Asset](../examples/asset/custom_asset.rs) | Implements a custom asset loader [Custom Asset IO](../examples/asset/custom_asset_reader.rs) | Implements a custom AssetReader [Embedded Asset](../examples/asset/embedded_asset.rs) | Embed an asset in the application binary and load it -[Extra asset source](../examples/asset/extra_source.rs) | Load an asset from a non-standard asset source -[HTTP Asset](../examples/asset/http_asset.rs) | Load an asset from a http source +[Extra Asset Source](../examples/asset/extra_source.rs) | Load an asset from a non-standard asset source +[HTTP Asset Source](../examples/asset/http_source.rs) | Load an asset from a http source [Hot Reloading of Assets](../examples/asset/hot_asset_reloading.rs) | Demonstrates automatic reloading of assets when modified on disk [Mult-asset synchronization](../examples/asset/multi_asset_sync.rs) | Demonstrates how to wait for multiple assets to be loaded. [Repeated texture configuration](../examples/asset/repeated_texture.rs) | How to configure the texture to repeat instead of the default clamp to edges From 777aaa9a48e3c3a23f51c6947c27a511efd370c3 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Fri, 6 Dec 2024 12:46:57 +1100 Subject: [PATCH 15/36] format --- crates/bevy_asset/src/http_source.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index 9c49348b35340..a34ca2db537d7 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -234,7 +234,8 @@ mod tests { #[test] fn make_https_without_extension_meta_uri() { assert_eq!( - HttpSourceAssetReader::Https.make_meta_uri(Path::new("s3.johanhelsing.studio/dump/favicon")), + HttpSourceAssetReader::Https + .make_meta_uri(Path::new("s3.johanhelsing.studio/dump/favicon")), None ); } From a82a9544b88ecdea942cd118f060f09f87f05517 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Fri, 6 Dec 2024 13:11:22 +1100 Subject: [PATCH 16/36] refactor: reuse HttpWasmAssetReader::fetch_bytes --- crates/bevy_asset/src/http_source.rs | 49 ++-------------------------- crates/bevy_asset/src/io/wasm.rs | 3 +- 2 files changed, 5 insertions(+), 47 deletions(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index a34ca2db537d7..33e1941fdcd8d 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -50,52 +50,9 @@ impl HttpSourceAssetReader { #[cfg(target_arch = "wasm32")] async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { - use crate::io::VecReader; - use js_sys::Uint8Array; - use wasm_bindgen::JsCast; - use wasm_bindgen_futures::JsFuture; - use web_sys::Response; - - fn js_value_to_err<'a>( - context: &'a str, - ) -> impl FnOnce(wasm_bindgen::JsValue) -> std::io::Error + 'a { - move |value| { - let message = match js_sys::JSON::stringify(&value) { - Ok(js_str) => format!("Failed to {context}: {js_str}"), - Err(_) => { - format!( - "Failed to {context} and also failed to stringify the JSValue of the error" - ) - } - }; - - std::io::Error::new(std::io::ErrorKind::Other, message) - } - } - - let window = web_sys::window().unwrap(); - let resp_value = JsFuture::from(window.fetch_with_str(path.to_str().unwrap())) - .await - .map_err(js_value_to_err("fetch path"))?; - let resp = resp_value - .dyn_into::() - .map_err(js_value_to_err("convert fetch to Response"))?; - match resp.status() { - 200 => { - let data = JsFuture::from(resp.array_buffer().unwrap()).await.unwrap(); - let bytes = Uint8Array::new(&data).to_vec(); - let reader = Box::new(VecReader::new(bytes)); - Ok(reader) - } - 404 => Err(AssetReaderError::NotFound(path)), - status => Err(AssetReaderError::Io( - std::io::Error::new( - std::io::ErrorKind::Other, - format!("Encountered unexpected HTTP status {status}"), - ) - .into(), - )), - } + use crate::io::wasm::HttpWasmAssetReader; + + HttpWasmAssetReader::new("").fetch_bytes(path).await } #[cfg(not(target_arch = "wasm32"))] diff --git a/crates/bevy_asset/src/io/wasm.rs b/crates/bevy_asset/src/io/wasm.rs index 25a5d223cbb0b..a9511b7ea19eb 100644 --- a/crates/bevy_asset/src/io/wasm.rs +++ b/crates/bevy_asset/src/io/wasm.rs @@ -51,7 +51,8 @@ fn js_value_to_err(context: &str) -> impl FnOnce(JsValue) -> std::io::Error + '_ } impl HttpWasmAssetReader { - async fn fetch_bytes<'a>(&self, path: PathBuf) -> Result { + // Also used by [`HttpSourceAssetReader`]. + pub(crate) async fn fetch_bytes<'a>(&self, path: PathBuf) -> Result { // The JS global scope includes a self-reference via a specializing name, which can be used to determine the type of global context available. let global: Global = js_sys::global().unchecked_into(); let promise = if !global.window().is_undefined() { From adbfc5ea2c9c67d92e2729b3d0fc087762974d81 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Fri, 6 Dec 2024 13:18:27 +1100 Subject: [PATCH 17/36] format --- crates/bevy_asset/src/http_source.rs | 2 +- crates/bevy_asset/src/io/wasm.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index 33e1941fdcd8d..e53bb6131f17a 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -51,7 +51,7 @@ impl HttpSourceAssetReader { #[cfg(target_arch = "wasm32")] async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { use crate::io::wasm::HttpWasmAssetReader; - + HttpWasmAssetReader::new("").fetch_bytes(path).await } diff --git a/crates/bevy_asset/src/io/wasm.rs b/crates/bevy_asset/src/io/wasm.rs index a9511b7ea19eb..1fac50dfc5374 100644 --- a/crates/bevy_asset/src/io/wasm.rs +++ b/crates/bevy_asset/src/io/wasm.rs @@ -52,7 +52,10 @@ fn js_value_to_err(context: &str) -> impl FnOnce(JsValue) -> std::io::Error + '_ impl HttpWasmAssetReader { // Also used by [`HttpSourceAssetReader`]. - pub(crate) async fn fetch_bytes<'a>(&self, path: PathBuf) -> Result { + pub(crate) async fn fetch_bytes<'a>( + &self, + path: PathBuf, + ) -> Result { // The JS global scope includes a self-reference via a specializing name, which can be used to determine the type of global context available. let global: Global = js_sys::global().unchecked_into(); let promise = if !global.window().is_undefined() { From 4225cf1c86fd518d0f8aaf200b04601d04881caf Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Fri, 6 Dec 2024 13:36:01 +1100 Subject: [PATCH 18/36] patch --- crates/bevy_asset/src/http_source.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index e53bb6131f17a..e1807b3a7e017 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -52,7 +52,10 @@ impl HttpSourceAssetReader { async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { use crate::io::wasm::HttpWasmAssetReader; - HttpWasmAssetReader::new("").fetch_bytes(path).await + HttpWasmAssetReader::new("") + .fetch_bytes(path) + .await + .map(|r| Box::new(r)) } #[cfg(not(target_arch = "wasm32"))] From fd8e6b35b34dbd9446ce838125a5b4227c807ad5 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Fri, 6 Dec 2024 13:42:35 +1100 Subject: [PATCH 19/36] fix: box reader --- crates/bevy_asset/src/http_source.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index e1807b3a7e017..dfd727b1469d4 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -55,7 +55,7 @@ async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { HttpWasmAssetReader::new("") .fetch_bytes(path) .await - .map(|r| Box::new(r)) + .map(|r| Box::new(r) as Box) } #[cfg(not(target_arch = "wasm32"))] From 3d51c1ebddf282c0e5e29a15e6213a7b7b9bf869 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Fri, 6 Dec 2024 17:37:23 +1100 Subject: [PATCH 20/36] docs: fix references --- crates/bevy_asset/src/http_source.rs | 2 -- crates/bevy_asset/src/io/wasm.rs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index dfd727b1469d4..b1c620fa13800 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -20,8 +20,6 @@ pub fn http_source_plugin(app: &mut App) { } /// Asset reader that treats paths as urls to load assets from. -/// This should not be confused with the [`HttpWasmAssetReader`] which is loads -/// *local* assets for wasm bevy apps. pub enum HttpSourceAssetReader { /// Unencrypted connections. Http, diff --git a/crates/bevy_asset/src/io/wasm.rs b/crates/bevy_asset/src/io/wasm.rs index 1fac50dfc5374..982d5d22f7540 100644 --- a/crates/bevy_asset/src/io/wasm.rs +++ b/crates/bevy_asset/src/io/wasm.rs @@ -51,7 +51,7 @@ fn js_value_to_err(context: &str) -> impl FnOnce(JsValue) -> std::io::Error + '_ } impl HttpWasmAssetReader { - // Also used by [`HttpSourceAssetReader`]. + // Also used by HttpSourceAssetReader pub(crate) async fn fetch_bytes<'a>( &self, path: PathBuf, From b46a4103d3c95f9600522fa80b29915f39c008d5 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Fri, 6 Dec 2024 18:03:33 +1100 Subject: [PATCH 21/36] feat: simple native asset cache --- .gitignore | 3 +- crates/bevy_asset/src/http_source.rs | 51 ++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 2a73629c590cf..7fb50ea86edaf 100644 --- a/.gitignore +++ b/.gitignore @@ -21,8 +21,7 @@ dxil.dll assets/**/*.meta crates/bevy_asset/imported_assets imported_assets -# Bevy Asset - http-cache-surf -http-cacache +.http-asset-cache # Bevy Examples example_showcase_config.ron diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index b1c620fa13800..185aa4b5f1f43 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -71,11 +71,18 @@ async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { ) })?; + if let Some(data) = http_asset_cache::try_load_from_cache(str_path)? { + return Ok(Box::new(VecReader::new(data))); + } + match ureq::get(str_path).call() { Ok(response) => { let mut reader = response.into_reader(); let mut buffer = Vec::new(); reader.read_to_end(&mut buffer)?; + + http_asset_cache::save_to_cache(str_path, &buffer)?; + Ok(Box::new(VecReader::new(buffer))) } // ureq considers all >=400 status codes as errors @@ -139,6 +146,50 @@ impl AssetReader for HttpSourceAssetReader { } } +/// A naive implementation of an HTTP asset cache that never invalidates. +/// `ureq` currently does not support caching, so this is a simple workaround. +/// It should eventually be replaced by `http-cache` or similar, see [tracking issue](https://github.com/06chaynes/http-cache/issues/91) +mod http_asset_cache { + use std::fs::{self, File}; + use std::io::{self, Read, Write}; + use std::path::PathBuf; + + const CACHE_DIR: &str = ".http-asset-cache"; + + fn url_to_filename(url: &str) -> String { + // Basic URL to filename conversion + // This is a naive implementation and might need more robust handling + url.replace([':', '/', '?', '=', '&'], "_") + } + + pub fn try_load_from_cache(url: &str) -> Result>, io::Error> { + let filename = url_to_filename(url); + let cache_path = PathBuf::from(CACHE_DIR).join(&filename); + + // Check if file exists in cache + if cache_path.exists() { + let mut file = File::open(&cache_path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + return Ok(Some(buffer)); + } else { + return Ok(None); + } + } + + pub fn save_to_cache(url: &str, data: &[u8]) -> Result<(), io::Error> { + let filename = url_to_filename(url); + let cache_path = PathBuf::from(CACHE_DIR).join(&filename); + + fs::create_dir_all(CACHE_DIR).ok(); + + let mut cache_file = File::create(&cache_path)?; + cache_file.write_all(data)?; + + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; From dca682aa174b21189154ddb176303ffa136d0d8d Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Fri, 6 Dec 2024 18:26:49 +1100 Subject: [PATCH 22/36] fix: clippy --- crates/bevy_asset/src/http_source.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index 185aa4b5f1f43..c0acd5c738c88 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -166,14 +166,13 @@ mod http_asset_cache { let filename = url_to_filename(url); let cache_path = PathBuf::from(CACHE_DIR).join(&filename); - // Check if file exists in cache if cache_path.exists() { let mut file = File::open(&cache_path)?; let mut buffer = Vec::new(); file.read_to_end(&mut buffer)?; - return Ok(Some(buffer)); + Ok(Some(buffer)) } else { - return Ok(None); + Ok(None) } } From 9c42d34c3ecfcc39cb3b61b7a1a2bd7f31d14e54 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Sat, 7 Dec 2024 15:52:16 +1100 Subject: [PATCH 23/36] use AssertReaderError::HttpError --- crates/bevy_asset/src/http_source.rs | 12 +----------- crates/bevy_asset/src/io/mod.rs | 3 ++- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index c0acd5c738c88..e13487b42b47b 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -90,17 +90,7 @@ async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { if code == 404 { Err(AssetReaderError::NotFound(path)) } else { - Err(AssetReaderError::Io( - io::Error::new( - io::ErrorKind::Other, - format!( - "unexpected status code {} while loading {}", - code, - path.display() - ), - ) - .into(), - )) + Err(AssetReaderError::HttpError(code)) } } Err(ureq::Error::Transport(err)) => Err(AssetReaderError::Io( diff --git a/crates/bevy_asset/src/io/mod.rs b/crates/bevy_asset/src/io/mod.rs index 207e51659b808..8e5ba107240e2 100644 --- a/crates/bevy_asset/src/io/mod.rs +++ b/crates/bevy_asset/src/io/mod.rs @@ -47,7 +47,8 @@ pub enum AssetReaderError { Io(Arc), /// The HTTP request completed but returned an unhandled [HTTP response status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status). - /// If the request fails before getting a status code (e.g. request timeout, interrupted connection, etc), expect [`AssetReaderError::Io`]. + /// - If the request returns a 404 error, expect [`AssetReaderError::NotFound`]. + /// - If the request fails before getting a status code (e.g. request timeout, interrupted connection, etc), expect [`AssetReaderError::Io`]. #[display("Encountered HTTP status {_0:?} when loading asset")] #[error(ignore)] HttpError(u16), From 6abb4e9b7e0e052d24c52c2cf7287e75c86afa60 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Sat, 7 Dec 2024 15:53:29 +1100 Subject: [PATCH 24/36] use hash instead of filename --- crates/bevy_asset/src/http_source.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index e13487b42b47b..21b29d732915b 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -140,20 +140,22 @@ impl AssetReader for HttpSourceAssetReader { /// `ureq` currently does not support caching, so this is a simple workaround. /// It should eventually be replaced by `http-cache` or similar, see [tracking issue](https://github.com/06chaynes/http-cache/issues/91) mod http_asset_cache { + use std::collections::hash_map::DefaultHasher; use std::fs::{self, File}; + use std::hash::{Hash, Hasher}; use std::io::{self, Read, Write}; use std::path::PathBuf; const CACHE_DIR: &str = ".http-asset-cache"; - fn url_to_filename(url: &str) -> String { - // Basic URL to filename conversion - // This is a naive implementation and might need more robust handling - url.replace([':', '/', '?', '=', '&'], "_") + fn url_to_hash(url: &str) -> String { + let mut hasher = DefaultHasher::new(); + url.hash(&mut hasher); + format!("{:x}", hasher.finish()) } pub fn try_load_from_cache(url: &str) -> Result>, io::Error> { - let filename = url_to_filename(url); + let filename = url_to_hash(url); let cache_path = PathBuf::from(CACHE_DIR).join(&filename); if cache_path.exists() { @@ -167,7 +169,7 @@ mod http_asset_cache { } pub fn save_to_cache(url: &str, data: &[u8]) -> Result<(), io::Error> { - let filename = url_to_filename(url); + let filename = url_to_hash(url); let cache_path = PathBuf::from(CACHE_DIR).join(&filename); fs::create_dir_all(CACHE_DIR).ok(); From d67677fd4497c1a3df11c10d087717595c6eb076 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Sat, 7 Dec 2024 05:50:37 +1100 Subject: [PATCH 25/36] prefer core to std --- crates/bevy_asset/src/http_source.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index 21b29d732915b..7e1ad0befb7ca 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -140,9 +140,9 @@ impl AssetReader for HttpSourceAssetReader { /// `ureq` currently does not support caching, so this is a simple workaround. /// It should eventually be replaced by `http-cache` or similar, see [tracking issue](https://github.com/06chaynes/http-cache/issues/91) mod http_asset_cache { + use core::hash::{Hash, Hasher}; use std::collections::hash_map::DefaultHasher; use std::fs::{self, File}; - use std::hash::{Hash, Hasher}; use std::io::{self, Read, Write}; use std::path::PathBuf; From 1a9fae2a865eb6320d0207f2e10c830904982a0c Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Thu, 12 Dec 2024 08:31:32 +1100 Subject: [PATCH 26/36] feat: top level `http_source` & `http_source_cache` features --- Cargo.toml | 6 ++++++ crates/bevy_asset/Cargo.toml | 2 +- crates/bevy_asset/src/http_source.rs | 3 +++ crates/bevy_internal/Cargo.toml | 6 ++++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 78c1ed69919ef..b778a739b06cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -433,6 +433,12 @@ file_watcher = ["bevy_internal/file_watcher"] # Enables watching in memory asset providers for Bevy Asset hot-reloading embedded_watcher = ["bevy_internal/embedded_watcher"] +# Enables using assets from HTTP sources +http_source = ["bevy_internal/http_source"] + +# Assets downloaded from HTTP sources are cached +http_source_cache = ["bevy_internal/http_source_cache"] + # Enable stepping-based debugging of Bevy systems bevy_debug_stepping = ["bevy_internal/bevy_debug_stepping"] diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index 92a8b212a9e83..a10ed9d726505 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -11,11 +11,11 @@ keywords = ["bevy"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = ["http_source"] file_watcher = ["notify-debouncer-full", "watch"] embedded_watcher = ["file_watcher"] multi_threaded = ["bevy_tasks/multi_threaded"] http_source = ["ureq"] +http_source_cache = [] asset_processor = [] watch = [] trace = [] diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index 7e1ad0befb7ca..8f1a0b8b9b857 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -71,6 +71,7 @@ async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { ) })?; + #[cfg(feature("http_source_cache"))] if let Some(data) = http_asset_cache::try_load_from_cache(str_path)? { return Ok(Box::new(VecReader::new(data))); } @@ -81,6 +82,7 @@ async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { let mut buffer = Vec::new(); reader.read_to_end(&mut buffer)?; + #[cfg(feature("http_source_cache"))] http_asset_cache::save_to_cache(str_path, &buffer)?; Ok(Box::new(VecReader::new(buffer))) @@ -139,6 +141,7 @@ impl AssetReader for HttpSourceAssetReader { /// A naive implementation of an HTTP asset cache that never invalidates. /// `ureq` currently does not support caching, so this is a simple workaround. /// It should eventually be replaced by `http-cache` or similar, see [tracking issue](https://github.com/06chaynes/http-cache/issues/91) +#[cfg(feature("http_source_cache"))] mod http_asset_cache { use core::hash::{Hash, Hasher}; use std::collections::hash_map::DefaultHasher; diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index c6f3fed2d9eed..297fd487b0aa5 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -190,6 +190,12 @@ debug_glam_assert = ["bevy_math/debug_glam_assert"] default_font = ["bevy_text?/default_font"] +# Enables using assets from HTTP sources +http_source = ["bevy_asset?/http_source"] + +# Assets downloaded from HTTP sources are cached +http_source_cache = ["bevy_asset?/http_source_cache"] + # Enables the built-in asset processor for processed assets. asset_processor = ["bevy_asset?/asset_processor"] From df8e119692659a9804efdce7adf326d411386dbb Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Thu, 12 Dec 2024 08:37:12 +1100 Subject: [PATCH 27/36] fix: http_source features --- crates/bevy_asset/src/http_source.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index 8f1a0b8b9b857..1792bc1650450 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -7,7 +7,7 @@ use std::path::{Path, PathBuf}; /// Adds the `http` and `https` asset sources to the app. /// Any asset path that begins with `http` or `https` will be loaded from the web -/// via `fetch`(wasm) or `surf`(native). +/// via `fetch`(wasm) or `ureq`(native). pub fn http_source_plugin(app: &mut App) { app.register_asset_source( "http", @@ -71,7 +71,7 @@ async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { ) })?; - #[cfg(feature("http_source_cache"))] + #[cfg(feature = "http_source_cache")] if let Some(data) = http_asset_cache::try_load_from_cache(str_path)? { return Ok(Box::new(VecReader::new(data))); } @@ -82,7 +82,7 @@ async fn get<'a>(path: PathBuf) -> Result, AssetReaderError> { let mut buffer = Vec::new(); reader.read_to_end(&mut buffer)?; - #[cfg(feature("http_source_cache"))] + #[cfg(feature = "http_source_cache")] http_asset_cache::save_to_cache(str_path, &buffer)?; Ok(Box::new(VecReader::new(buffer))) @@ -141,7 +141,7 @@ impl AssetReader for HttpSourceAssetReader { /// A naive implementation of an HTTP asset cache that never invalidates. /// `ureq` currently does not support caching, so this is a simple workaround. /// It should eventually be replaced by `http-cache` or similar, see [tracking issue](https://github.com/06chaynes/http-cache/issues/91) -#[cfg(feature("http_source_cache"))] +#[cfg(feature = "http_source_cache")] mod http_asset_cache { use core::hash::{Hash, Hasher}; use std::collections::hash_map::DefaultHasher; From eb71d70eb4196d1a9af82409c5ab2b22a3e21883 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Thu, 12 Dec 2024 10:05:19 +1100 Subject: [PATCH 28/36] docs: update cargo features --- docs/cargo_features.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/cargo_features.md b/docs/cargo_features.md index 1cc83b9e1102f..349b1797f1fcf 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -76,6 +76,8 @@ The default feature set enables most of the expected features of a game engine, |ghost_nodes|Experimental support for nodes that are ignored for UI layouting| |gif|GIF image format support| |glam_assert|Enable assertions to check the validity of parameters passed to glam| +|http_source|Enables using assets from HTTP sources| +|http_source_cache|Assets downloaded from HTTP sources are cached| |ico|ICO image format support| |ios_simulator|Enable support for the ios_simulator by downgrading some rendering capabilities| |jpeg|JPEG image format support| From e1fc632982fce34ec0cf49339903356b6cc62dcc Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Thu, 12 Dec 2024 16:40:38 +1100 Subject: [PATCH 29/36] Add required feature for http_source example Co-authored-by: jf908 --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index b778a739b06cb..1f4d6282ebe98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1659,6 +1659,7 @@ name = "HTTP Asset Source" description = "Load an asset from a http source" category = "Assets" wasm = true +required-features = ["http_source"] [[example]] name = "hot_asset_reloading" From 27f69f9cbb3123f1cfafb39759e25070228ccb43 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Sat, 14 Dec 2024 23:30:20 +1100 Subject: [PATCH 30/36] use example.com url --- .gitignore | 1 + crates/bevy_asset/src/http_source.rs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 7fb50ea86edaf..95d6c4b3ac6c3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ crates/**/target benches/**/target tools/**/target **/*.rs.bk +rustc-ice-* # Cargo Cargo.lock diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index 1792bc1650450..9d4ed7a004e85 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -192,10 +192,10 @@ mod tests { fn make_http_uri() { assert_eq!( HttpSourceAssetReader::Http - .make_uri(Path::new("s3.johanhelsing.studio/dump/favicon.png")) + .make_uri(Path::new("example.com/favicon.png")) .to_str() .unwrap(), - "http://s3.johanhelsing.studio/dump/favicon.png" + "http://example.com/favicon.png" ); } @@ -203,10 +203,10 @@ mod tests { fn make_https_uri() { assert_eq!( HttpSourceAssetReader::Https - .make_uri(Path::new("s3.johanhelsing.studio/dump/favicon.png")) + .make_uri(Path::new("example.com/favicon.png")) .to_str() .unwrap(), - "https://s3.johanhelsing.studio/dump/favicon.png" + "https://example.com/favicon.png" ); } @@ -214,11 +214,11 @@ mod tests { fn make_http_meta_uri() { assert_eq!( HttpSourceAssetReader::Http - .make_meta_uri(Path::new("s3.johanhelsing.studio/dump/favicon.png")) + .make_meta_uri(Path::new("example.com/favicon.png")) .expect("cannot create meta uri") .to_str() .unwrap(), - "http://s3.johanhelsing.studio/dump/favicon.png.meta" + "http://example.com/favicon.png.meta" ); } @@ -226,11 +226,11 @@ mod tests { fn make_https_meta_uri() { assert_eq!( HttpSourceAssetReader::Https - .make_meta_uri(Path::new("s3.johanhelsing.studio/dump/favicon.png")) + .make_meta_uri(Path::new("example.com/favicon.png")) .expect("cannot create meta uri") .to_str() .unwrap(), - "https://s3.johanhelsing.studio/dump/favicon.png.meta" + "https://example.com/favicon.png.meta" ); } @@ -238,7 +238,7 @@ mod tests { fn make_https_without_extension_meta_uri() { assert_eq!( HttpSourceAssetReader::Https - .make_meta_uri(Path::new("s3.johanhelsing.studio/dump/favicon")), + .make_meta_uri(Path::new("example.com/favicon")), None ); } From 5d87bb11d23f4bfea15b3fc592ab4e9ae3ba9df9 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Sat, 14 Dec 2024 23:34:47 +1100 Subject: [PATCH 31/36] format --- crates/bevy_asset/src/http_source.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index 9d4ed7a004e85..4c08309a1c5c6 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -237,8 +237,7 @@ mod tests { #[test] fn make_https_without_extension_meta_uri() { assert_eq!( - HttpSourceAssetReader::Https - .make_meta_uri(Path::new("example.com/favicon")), + HttpSourceAssetReader::Https.make_meta_uri(Path::new("example.com/favicon")), None ); } From 0d58f97f4765e61c23668164fca1d33dd43e9941 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Wed, 18 Dec 2024 16:45:33 +1100 Subject: [PATCH 32/36] ureq default-features=false --- Cargo.toml | 2 +- crates/bevy_asset/Cargo.toml | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0fe22aeb27989..9e8c7b8e45acb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1656,13 +1656,13 @@ wasm = false name = "http_source" path = "examples/asset/http_source.rs" doc-scrape-examples = true +required-features = ["http_source"] [package.metadata.example.http_source] name = "HTTP Asset Source" description = "Load an asset from a http source" category = "Assets" wasm = true -required-features = ["http_source"] [[example]] name = "hot_asset_reloading" diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index a10ed9d726505..84668147f6bb4 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -65,7 +65,11 @@ js-sys = "0.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] notify-debouncer-full = { version = "0.4.0", optional = true } -ureq = { version = "2.10", optional = true } +ureq = { version = "2.10", optional = true, default-features = false, features = [ + "brotli", + "gzip", + "tls", +] } [dev-dependencies] bevy_core = { path = "../bevy_core", version = "0.15.0-dev" } From 20b6ccacf7e11dc13854e80d93718944bf5b4c37 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Tue, 24 Dec 2024 12:13:12 +1100 Subject: [PATCH 33/36] allow "Unicode-3.0" & "MPL-2.0" licenses --- deny.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deny.toml b/deny.toml index f8114fed1d1a7..e7b06756ef61d 100644 --- a/deny.toml +++ b/deny.toml @@ -21,7 +21,9 @@ allow = [ "ISC", "MIT", "MIT-0", + "MPL-2.0", "Unlicense", + "Unicode-3.0", "Zlib", ] From b423a924aedeeaa99f7a3ab0cf48e7bbaee59e8b Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Tue, 24 Dec 2024 12:42:10 +1100 Subject: [PATCH 34/36] ureq disable default features --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9e8c7b8e45acb..54a2643c63e1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -502,7 +502,7 @@ accesskit = "0.17" smol = "2" smol-macros = "0.1" smol-hyper = "0.1" -ureq = { version = "2.10.1", features = ["json"] } +ureq = { version = "2.10", default-features = false, features = ["json"] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen = { version = "0.2" } From c6752872fbfa5b400ebd1beb224838a0c0385d1a Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Tue, 24 Dec 2024 16:17:42 +1100 Subject: [PATCH 35/36] remove ureq tls feature --- Cargo.toml | 2 +- crates/bevy_asset/Cargo.toml | 2 +- crates/bevy_asset/src/http_source.rs | 4 ++++ examples/asset/http_source.rs | 4 ++++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 54a2643c63e1d..404638207e01d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -502,7 +502,7 @@ accesskit = "0.17" smol = "2" smol-macros = "0.1" smol-hyper = "0.1" -ureq = { version = "2.10", default-features = false, features = ["json"] } +ureq = { version = "2.10", default-features = false, features = ["json", "tls"] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen = { version = "0.2" } diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index 84668147f6bb4..1e57abe9e3cad 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -68,7 +68,7 @@ notify-debouncer-full = { version = "0.4.0", optional = true } ureq = { version = "2.10", optional = true, default-features = false, features = [ "brotli", "gzip", - "tls", + # we should add tls feature when ring gets a cleaner license https://github.com/briansmith/ring/issues/1827 ] } [dev-dependencies] diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index 4c08309a1c5c6..654a9b2cedd9e 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -8,6 +8,10 @@ use std::path::{Path, PathBuf}; /// Adds the `http` and `https` asset sources to the app. /// Any asset path that begins with `http` or `https` will be loaded from the web /// via `fetch`(wasm) or `ureq`(native). +/// Note that the use of secure `https` sources in non-wasm builds requires the following dependency: +/// ```toml +/// ureq = { version = "*", features = ["tls"] } +/// ``` pub fn http_source_plugin(app: &mut App) { app.register_asset_source( "http", diff --git a/examples/asset/http_source.rs b/examples/asset/http_source.rs index 27ee70099b449..5ff9c0d32aff7 100644 --- a/examples/asset/http_source.rs +++ b/examples/asset/http_source.rs @@ -1,4 +1,8 @@ //! Example usage of the `http` asset source to load assets from the web. +/// Note that the use of secure `https` sources in non-wasm builds requires the following dependency: +//! ```toml +//! ureq = { version = "*", features = ["tls"] } +//! ``` use bevy::prelude::*; fn main() { From 88d04f6b75a147cd339ce23e976f521fc37bf700 Mon Sep 17 00:00:00 2001 From: Peter Hayman Date: Tue, 24 Dec 2024 19:21:29 +1100 Subject: [PATCH 36/36] docs: add license issue context --- crates/bevy_asset/src/http_source.rs | 5 ++++- examples/asset/http_source.rs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/bevy_asset/src/http_source.rs b/crates/bevy_asset/src/http_source.rs index 654a9b2cedd9e..a26eb8de6da31 100644 --- a/crates/bevy_asset/src/http_source.rs +++ b/crates/bevy_asset/src/http_source.rs @@ -8,7 +8,10 @@ use std::path::{Path, PathBuf}; /// Adds the `http` and `https` asset sources to the app. /// Any asset path that begins with `http` or `https` will be loaded from the web /// via `fetch`(wasm) or `ureq`(native). -/// Note that the use of secure `https` sources in non-wasm builds requires the following dependency: +/// +/// Due to [licensing complexities](https://github.com/briansmith/ring/issues/1827) +/// secure `https` requests are disabled by default in non-wasm builds. +/// To enable add this to your dependencies in Cargo.toml: /// ```toml /// ureq = { version = "*", features = ["tls"] } /// ``` diff --git a/examples/asset/http_source.rs b/examples/asset/http_source.rs index 5ff9c0d32aff7..811e7bc977bf2 100644 --- a/examples/asset/http_source.rs +++ b/examples/asset/http_source.rs @@ -1,5 +1,8 @@ //! Example usage of the `http` asset source to load assets from the web. -/// Note that the use of secure `https` sources in non-wasm builds requires the following dependency: +//! +//! Due to [licensing complexities](https://github.com/briansmith/ring/issues/1827) +//! secure `https` requests are disabled by default in non-wasm builds. +//! To enable add this to your dependencies in Cargo.toml: //! ```toml //! ureq = { version = "*", features = ["tls"] } //! ```