Replace all http code in epi/eframe/egui_glium/egui_web with ehttp (#697)
I've extracted all the http request code and turned it into its own crate at <https://github.com/emilk/ehttp>. There was never a reason for the HTTP request library to be part of `eframe`. Much better to have it as its own crate!
This commit is contained in:
parent
de1a1ba9b2
commit
9598596bdc
22 changed files with 49 additions and 330 deletions
15
Cargo.lock
generated
15
Cargo.lock
generated
|
@ -810,6 +810,7 @@ version = "0.14.0"
|
|||
dependencies = [
|
||||
"criterion",
|
||||
"egui",
|
||||
"ehttp",
|
||||
"epi",
|
||||
"image",
|
||||
"serde",
|
||||
|
@ -830,7 +831,6 @@ dependencies = [
|
|||
"ron",
|
||||
"serde",
|
||||
"tts",
|
||||
"ureq",
|
||||
"webbrowser",
|
||||
]
|
||||
|
||||
|
@ -849,6 +849,19 @@ dependencies = [
|
|||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ehttp"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b078e2305de4c998700ac152b3e7a358d7fbe77e15b3b1cd2c44a8b82176124f"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"ureq",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
|
|
|
@ -3,6 +3,7 @@ All notable changes to the `eframe` crate.
|
|||
|
||||
|
||||
## Unreleased
|
||||
* Remove "http" feature (use https://github.com/emilk/ehttp instead!).
|
||||
|
||||
|
||||
## 0.14.0 - 2021-08-24
|
||||
|
|
|
@ -40,7 +40,6 @@ default = ["default_fonts"]
|
|||
# If set, egui will use `include_bytes!` to bundle some fonts.
|
||||
# If you plan on specifying your own fonts you may disable this feature.
|
||||
default_fonts = ["egui/default_fonts"]
|
||||
http = ["egui_glium/http", "egui_web/http"]
|
||||
persistence = ["epi/persistence", "egui_glium/persistence", "egui_web/persistence"]
|
||||
screen_reader = ["egui_glium/screen_reader", "egui_web/screen_reader"] # experimental
|
||||
time = ["egui_glium/time"] # for seconds_since_midnight
|
||||
|
|
|
@ -17,6 +17,9 @@ include = [
|
|||
"Cargo.toml",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[lib]
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -15,7 +15,7 @@ egui_demo_lib = { version = "0.14.0", path = "../egui_demo_lib", features = ["ex
|
|||
|
||||
[features]
|
||||
default = ["persistence"]
|
||||
http = ["eframe/http", "egui_demo_lib/http"]
|
||||
http = ["egui_demo_lib/http"]
|
||||
persistence = ["eframe/persistence", "egui_demo_lib/persistence"]
|
||||
screen_reader = ["eframe/screen_reader"] # experimental
|
||||
syntax_highlighting = ["egui_demo_lib/syntax_highlighting"]
|
||||
|
|
|
@ -28,6 +28,7 @@ epi = { version = "0.14.0", path = "../epi" }
|
|||
unicode_names2 = { version = "0.4.0", default-features = false }
|
||||
|
||||
# feature "http":
|
||||
ehttp = { version = "0.1.0", optional = true }
|
||||
image = { version = "0.23", default-features = false, features = ["jpeg", "png"], optional = true }
|
||||
|
||||
# feature "syntax_highlighting":
|
||||
|
@ -41,7 +42,7 @@ criterion = { version = "0.3", default-features = false }
|
|||
|
||||
[features]
|
||||
default = []
|
||||
http = ["image", "epi/http"]
|
||||
http = ["ehttp", "image"]
|
||||
persistence = ["egui/persistence", "epi/persistence", "serde"]
|
||||
syntax_highlighting = ["syntect"]
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ impl super::View for CodeEditor {
|
|||
} else {
|
||||
ui.horizontal_wrapped(|ui|{
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("Compile the demo with the 'syntect' feature to enable much nicer syntax highlighting using ");
|
||||
ui.label("Compile the demo with the 'syntax_highlighting' feature to enable much nicer syntax highlighting using ");
|
||||
ui.hyperlink_to("syntect", "https://github.com/trishume/syntect");
|
||||
ui.label(".");
|
||||
});
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use epi::http::{Request, Response};
|
||||
use std::sync::mpsc::Receiver;
|
||||
|
||||
struct Resource {
|
||||
/// HTTP response
|
||||
response: Response,
|
||||
response: ehttp::Response,
|
||||
|
||||
text: Option<String>,
|
||||
|
||||
|
@ -15,7 +14,7 @@ struct Resource {
|
|||
}
|
||||
|
||||
impl Resource {
|
||||
fn from_response(response: Response) -> Self {
|
||||
fn from_response(response: ehttp::Response) -> Self {
|
||||
let content_type = response.content_type().unwrap_or_default();
|
||||
let image = if content_type.starts_with("image/") {
|
||||
Image::decode(&response.bytes)
|
||||
|
@ -54,7 +53,7 @@ pub struct HttpApp {
|
|||
request_body: String,
|
||||
|
||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||
in_progress: Option<Receiver<Result<Response, String>>>,
|
||||
in_progress: Option<Receiver<Result<ehttp::Response, String>>>,
|
||||
|
||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||
result: Option<Result<Resource, String>>,
|
||||
|
@ -94,9 +93,15 @@ impl epi::App for HttpApp {
|
|||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("HTTP Fetch Example");
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("HTTP requests made using ");
|
||||
ui.hyperlink_to("ehttp", "https://www.github.com/emilk/ehttp");
|
||||
ui.label(".");
|
||||
});
|
||||
ui.add(egui::github_link_file!(
|
||||
"https://github.com/emilk/egui/blob/master/",
|
||||
"(source code)"
|
||||
"(demo source code)"
|
||||
));
|
||||
|
||||
if let Some(request) = ui_url(
|
||||
|
@ -110,7 +115,7 @@ impl epi::App for HttpApp {
|
|||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
self.in_progress = Some(receiver);
|
||||
|
||||
frame.http_fetch(request, move |response| {
|
||||
ehttp::fetch(request, move |response| {
|
||||
sender.send(response).ok();
|
||||
repaint_signal.request_repaint();
|
||||
});
|
||||
|
@ -144,7 +149,7 @@ fn ui_url(
|
|||
url: &mut String,
|
||||
method: &mut Method,
|
||||
request_body: &mut String,
|
||||
) -> Option<Request> {
|
||||
) -> Option<ehttp::Request> {
|
||||
let mut trigger_fetch = false;
|
||||
|
||||
egui::Grid::new("request_params").show(ui, |ui| {
|
||||
|
@ -202,8 +207,8 @@ fn ui_url(
|
|||
|
||||
if trigger_fetch {
|
||||
Some(match *method {
|
||||
Method::Get => Request::get(url),
|
||||
Method::Post => Request::post(url, request_body),
|
||||
Method::Get => ehttp::Request::get(url),
|
||||
Method::Post => ehttp::Request::post(url, request_body),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
|
@ -284,7 +289,7 @@ fn ui_resource(
|
|||
// Syntax highlighting:
|
||||
|
||||
#[cfg(feature = "syntect")]
|
||||
fn syntax_highlighting(response: &Response, text: &str) -> Option<ColoredText> {
|
||||
fn syntax_highlighting(response: &ehttp::Response, text: &str) -> Option<ColoredText> {
|
||||
let extension_and_rest: Vec<&str> = response.url.rsplitn(2, '.').collect();
|
||||
let extension = extension_and_rest.get(0)?;
|
||||
ColoredText::text_with_extension(text, extension)
|
||||
|
@ -361,6 +366,7 @@ impl ColoredText {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "syntect")]
|
||||
fn as_byte_range(whole: &str, range: &str) -> std::ops::Range<usize> {
|
||||
let whole_start = whole.as_ptr() as usize;
|
||||
let range_start = range.as_ptr() as usize;
|
||||
|
@ -371,7 +377,7 @@ fn as_byte_range(whole: &str, range: &str) -> std::ops::Range<usize> {
|
|||
}
|
||||
|
||||
#[cfg(not(feature = "syntect"))]
|
||||
fn syntax_highlighting(_: &Response, _: &str) -> Option<ColoredText> {
|
||||
fn syntax_highlighting(_: &ehttp::Response, _: &str) -> Option<ColoredText> {
|
||||
None
|
||||
}
|
||||
#[cfg(not(feature = "syntect"))]
|
||||
|
|
|
@ -4,6 +4,7 @@ All notable changes to the `egui_glium` integration will be noted in this file.
|
|||
|
||||
|
||||
## Unreleased
|
||||
* Remove "http" feature (use https://github.com/emilk/ehttp instead!).
|
||||
|
||||
|
||||
## 0.14.0 - 2021-08-24
|
||||
|
|
|
@ -28,9 +28,6 @@ epi = { version = "0.14.0", path = "../epi" }
|
|||
glium = "0.30"
|
||||
webbrowser = "0.5"
|
||||
|
||||
# feature "http":
|
||||
ureq = { version = "2.0", optional = true }
|
||||
|
||||
# feature "persistence":
|
||||
directories-next = { version = "2", optional = true }
|
||||
ron = { version = "0.6", optional = true }
|
||||
|
@ -48,7 +45,6 @@ default = ["default_fonts"]
|
|||
# If set, egui will use `include_bytes!` to bundle some fonts.
|
||||
# If you plan on specifying your own fonts you may disable this feature.
|
||||
default_fonts = ["egui/default_fonts"]
|
||||
http = ["epi/http", "ureq"]
|
||||
persistence = [
|
||||
"directories-next",
|
||||
"egui/persistence",
|
||||
|
|
|
@ -177,9 +177,6 @@ pub fn run(mut app: Box<dyn epi::App>, native_options: epi::NativeOptions) {
|
|||
#[allow(unused_mut)]
|
||||
let mut storage = create_storage(app.name());
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
let http = std::sync::Arc::new(crate::http::GliumHttp {});
|
||||
|
||||
let window_settings = deserialize_window_settings(&storage);
|
||||
let mut event_loop = glutin::event_loop::EventLoop::with_user_event();
|
||||
let icon = native_options.icon_data.clone().and_then(load_icon);
|
||||
|
@ -198,8 +195,6 @@ pub fn run(mut app: Box<dyn epi::App>, native_options: epi::NativeOptions) {
|
|||
let mut frame = epi::backend::FrameBuilder {
|
||||
info: integration_info(&display, None),
|
||||
tex_allocator: painter,
|
||||
#[cfg(feature = "http")]
|
||||
http: http.clone(),
|
||||
output: &mut app_output,
|
||||
repaint_signal: repaint_signal.clone(),
|
||||
}
|
||||
|
@ -222,8 +217,6 @@ pub fn run(mut app: Box<dyn epi::App>, native_options: epi::NativeOptions) {
|
|||
let mut frame = epi::backend::FrameBuilder {
|
||||
info: integration_info(&display, None),
|
||||
tex_allocator: painter,
|
||||
#[cfg(feature = "http")]
|
||||
http: http.clone(),
|
||||
output: &mut app_output,
|
||||
repaint_signal: repaint_signal.clone(),
|
||||
}
|
||||
|
@ -319,8 +312,6 @@ pub fn run(mut app: Box<dyn epi::App>, native_options: epi::NativeOptions) {
|
|||
let mut frame = epi::backend::FrameBuilder {
|
||||
info: integration_info(&display, previous_frame_time),
|
||||
tex_allocator: painter,
|
||||
#[cfg(feature = "http")]
|
||||
http: http.clone(),
|
||||
output: &mut app_output,
|
||||
repaint_signal: repaint_signal.clone(),
|
||||
}
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
pub use epi::http::{Request, Response};
|
||||
|
||||
/// NOTE: Ok(..) is returned on network error.
|
||||
/// Err is only for failure to use the fetch api.
|
||||
pub fn fetch_blocking(request: &Request) -> Result<Response, String> {
|
||||
let mut req = ureq::request(&request.method, &request.url);
|
||||
|
||||
for header in &request.headers {
|
||||
req = req.set(header.0, header.1);
|
||||
}
|
||||
|
||||
let resp = if request.body.is_empty() {
|
||||
req.call()
|
||||
} else {
|
||||
req.send_bytes(&request.body)
|
||||
};
|
||||
|
||||
let (ok, resp) = match resp {
|
||||
Ok(resp) => (true, resp),
|
||||
Err(ureq::Error::Status(_, resp)) => (false, resp), // Still read the body on e.g. 404
|
||||
Err(ureq::Error::Transport(error)) => return Err(error.to_string()),
|
||||
};
|
||||
|
||||
let url = resp.get_url().to_owned();
|
||||
let status = resp.status();
|
||||
let status_text = resp.status_text().to_owned();
|
||||
let mut headers = BTreeMap::new();
|
||||
for key in &resp.headers_names() {
|
||||
if let Some(value) = resp.header(key) {
|
||||
// lowercase for easy lookup
|
||||
headers.insert(key.to_ascii_lowercase(), value.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
let mut reader = resp.into_reader();
|
||||
let mut bytes = vec![];
|
||||
use std::io::Read;
|
||||
reader
|
||||
.read_to_end(&mut bytes)
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
let response = Response {
|
||||
url,
|
||||
ok,
|
||||
status,
|
||||
status_text,
|
||||
bytes,
|
||||
headers,
|
||||
};
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub(crate) struct GliumHttp {}
|
||||
|
||||
impl epi::backend::Http for GliumHttp {
|
||||
fn fetch_dyn(
|
||||
&self,
|
||||
request: Request,
|
||||
on_done: Box<dyn FnOnce(Result<Response, String>) + Send>,
|
||||
) {
|
||||
std::thread::spawn(move || {
|
||||
let result = crate::http::fetch_blocking(&request);
|
||||
on_done(result)
|
||||
});
|
||||
}
|
||||
}
|
|
@ -19,8 +19,6 @@
|
|||
#![allow(clippy::manual_range_contains, clippy::single_match)]
|
||||
|
||||
mod backend;
|
||||
#[cfg(feature = "http")]
|
||||
pub mod http;
|
||||
mod painter;
|
||||
#[cfg(feature = "persistence")]
|
||||
pub mod persistence;
|
||||
|
|
|
@ -4,6 +4,7 @@ All notable changes to the `egui_web` integration will be noted in this file.
|
|||
|
||||
|
||||
## Unreleased
|
||||
* Remove "http" feature (use https://github.com/emilk/ehttp instead!).
|
||||
|
||||
|
||||
## 0.14.1 - 2021-08-28
|
||||
|
|
|
@ -42,14 +42,6 @@ default = ["default_fonts"]
|
|||
# If set, egui will use `include_bytes!` to bundle some fonts.
|
||||
# If you plan on specifying your own fonts you may disable this feature.
|
||||
default_fonts = ["egui/default_fonts"]
|
||||
http = [
|
||||
"epi/http",
|
||||
"web-sys/Headers",
|
||||
"web-sys/Request",
|
||||
"web-sys/RequestInit",
|
||||
"web-sys/RequestMode",
|
||||
"web-sys/Response",
|
||||
]
|
||||
persistence = ["egui/persistence", "ron", "serde"]
|
||||
screen_reader = ["tts"] # experimental
|
||||
|
||||
|
|
|
@ -137,8 +137,6 @@ pub struct AppRunner {
|
|||
prefer_dark_mode: Option<bool>,
|
||||
last_save_time: f64,
|
||||
screen_reader: crate::screen_reader::ScreenReader,
|
||||
#[cfg(feature = "http")]
|
||||
http: Arc<http::WebHttp>,
|
||||
pub(crate) last_text_cursor_pos: Option<egui::Pos2>,
|
||||
}
|
||||
|
||||
|
@ -165,8 +163,6 @@ impl AppRunner {
|
|||
prefer_dark_mode,
|
||||
last_save_time: now_sec(),
|
||||
screen_reader: Default::default(),
|
||||
#[cfg(feature = "http")]
|
||||
http: Arc::new(http::WebHttp {}),
|
||||
last_text_cursor_pos: None,
|
||||
};
|
||||
|
||||
|
@ -175,8 +171,6 @@ impl AppRunner {
|
|||
let mut frame = epi::backend::FrameBuilder {
|
||||
info: runner.integration_info(),
|
||||
tex_allocator: runner.web_backend.painter.as_tex_allocator(),
|
||||
#[cfg(feature = "http")]
|
||||
http: runner.http.clone(),
|
||||
output: &mut app_output,
|
||||
repaint_signal: runner.needs_repaint.clone(),
|
||||
}
|
||||
|
@ -247,8 +241,6 @@ impl AppRunner {
|
|||
let mut frame = epi::backend::FrameBuilder {
|
||||
info: self.integration_info(),
|
||||
tex_allocator: self.web_backend.painter.as_tex_allocator(),
|
||||
#[cfg(feature = "http")]
|
||||
http: self.http.clone(),
|
||||
output: &mut app_output,
|
||||
repaint_signal: self.needs_repaint.clone(),
|
||||
}
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub use epi::http::{Request, Response};
|
||||
|
||||
/// NOTE: Ok(..) is returned on network error.
|
||||
/// Err is only for failure to use the fetch api.
|
||||
pub async fn fetch_async(request: &Request) -> Result<Response, String> {
|
||||
fetch_jsvalue(request)
|
||||
.await
|
||||
.map_err(|err| err.as_string().unwrap_or(format!("{:#?}", err)))
|
||||
}
|
||||
|
||||
/// NOTE: Ok(..) is returned on network error.
|
||||
/// Err is only for failure to use the fetch api.
|
||||
async fn fetch_jsvalue(request: &Request) -> Result<Response, JsValue> {
|
||||
// https://rustwasm.github.io/wasm-bindgen/examples/fetch.html
|
||||
// https://github.com/seanmonstar/reqwest/blob/master/src/wasm/client.rs
|
||||
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
|
||||
let mut opts = web_sys::RequestInit::new();
|
||||
opts.method(&request.method);
|
||||
opts.mode(web_sys::RequestMode::Cors);
|
||||
|
||||
if !request.body.is_empty() {
|
||||
let body_bytes: &[u8] = &request.body;
|
||||
let body_array: js_sys::Uint8Array = body_bytes.into();
|
||||
let js_value: &JsValue = body_array.as_ref();
|
||||
opts.body(Some(js_value));
|
||||
}
|
||||
|
||||
let js_request = web_sys::Request::new_with_str_and_init(&request.url, &opts)?;
|
||||
|
||||
for h in &request.headers {
|
||||
js_request.headers().set(h.0, h.1)?;
|
||||
}
|
||||
|
||||
let window = web_sys::window().unwrap();
|
||||
let response = JsFuture::from(window.fetch_with_request(&js_request)).await?;
|
||||
let response: web_sys::Response = response.dyn_into()?;
|
||||
|
||||
let array_buffer = JsFuture::from(response.array_buffer()?).await?;
|
||||
let uint8_array = js_sys::Uint8Array::new(&array_buffer);
|
||||
let bytes = uint8_array.to_vec();
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Headers
|
||||
// "Note: When Header values are iterated over, [...] values from duplicate header names are combined."
|
||||
let mut headers = std::collections::BTreeMap::<String, String>::new();
|
||||
let js_headers: web_sys::Headers = response.headers();
|
||||
let js_iter = js_sys::try_iter(&js_headers)
|
||||
.expect("headers try_iter")
|
||||
.expect("headers have an iterator");
|
||||
|
||||
for item in js_iter {
|
||||
let item = item.expect("headers iterator");
|
||||
let array: js_sys::Array = item.into();
|
||||
let v: Vec<JsValue> = array.to_vec();
|
||||
|
||||
let mut key = v[0]
|
||||
.as_string()
|
||||
.ok_or_else(|| JsValue::from_str("headers name"))?;
|
||||
let value = v[1]
|
||||
.as_string()
|
||||
.ok_or_else(|| JsValue::from_str("headers value"))?;
|
||||
|
||||
// for easy lookup
|
||||
key.make_ascii_lowercase();
|
||||
headers.insert(key, value);
|
||||
}
|
||||
|
||||
Ok(Response {
|
||||
url: response.url(),
|
||||
ok: response.ok(),
|
||||
status: response.status(),
|
||||
status_text: response.status_text(),
|
||||
bytes,
|
||||
headers,
|
||||
})
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub(crate) struct WebHttp {}
|
||||
|
||||
impl epi::backend::Http for WebHttp {
|
||||
fn fetch_dyn(
|
||||
&self,
|
||||
request: Request,
|
||||
on_done: Box<dyn FnOnce(Result<Response, String>) + Send>,
|
||||
) {
|
||||
crate::spawn_future(async move {
|
||||
let result = crate::http::fetch_async(&request).await;
|
||||
on_done(result)
|
||||
});
|
||||
}
|
||||
}
|
|
@ -22,8 +22,6 @@
|
|||
#![warn(clippy::all, rust_2018_idioms)]
|
||||
|
||||
pub mod backend;
|
||||
#[cfg(feature = "http")]
|
||||
pub mod http;
|
||||
mod painter;
|
||||
pub mod screen_reader;
|
||||
pub mod webgl1;
|
||||
|
|
|
@ -17,6 +17,9 @@ include = [
|
|||
"Cargo.toml",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[lib]
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -19,6 +19,9 @@ include = [
|
|||
"fonts/*.txt",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[lib]
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -29,5 +29,4 @@ serde = { version = "1", optional = true }
|
|||
|
||||
[features]
|
||||
default = []
|
||||
http = []
|
||||
persistence = ["ron", "serde"]
|
||||
|
|
115
epi/src/lib.rs
115
epi/src/lib.rs
|
@ -1,6 +1,6 @@
|
|||
//! Backend-agnostic interface for writing apps using [`egui`].
|
||||
//!
|
||||
//! `epi` provides interfaces for window management, serialization and http requests.
|
||||
//! `epi` provides interfaces for window management and serialization.
|
||||
//! An app written for `epi` can then be plugged into [`eframe`](https://docs.rs/eframe),
|
||||
//! the egui framework crate.
|
||||
//!
|
||||
|
@ -221,7 +221,7 @@ pub struct IconData {
|
|||
/// Represents the surroundings of your app.
|
||||
///
|
||||
/// It provides methods to inspect the surroundings (are we on the web?),
|
||||
/// allocate textures, do http requests, and change settings (e.g. window size).
|
||||
/// allocate textures, and change settings (e.g. window size).
|
||||
pub struct Frame<'a>(backend::FrameBuilder<'a>);
|
||||
|
||||
impl<'a> Frame<'a> {
|
||||
|
@ -255,19 +255,6 @@ impl<'a> Frame<'a> {
|
|||
pub fn repaint_signal(&self) -> std::sync::Arc<dyn RepaintSignal> {
|
||||
self.0.repaint_signal.clone()
|
||||
}
|
||||
|
||||
/// Very simple Http fetch API.
|
||||
/// Calls the given callback when done.
|
||||
///
|
||||
/// You must enable the "http" feature for this.
|
||||
#[cfg(feature = "http")]
|
||||
pub fn http_fetch(
|
||||
&self,
|
||||
request: http::Request,
|
||||
on_done: impl 'static + Send + FnOnce(Result<http::Response, http::Error>),
|
||||
) {
|
||||
self.0.http.fetch_dyn(request, Box::new(on_done))
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about the web environment (if applicable).
|
||||
|
@ -374,114 +361,16 @@ pub const APP_KEY: &str = "app";
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
/// `epi` supports simple HTTP requests with [`Frame::http_fetch`].
|
||||
///
|
||||
/// You must enable the "http" feature for this.
|
||||
pub mod http {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// A simple http request.
|
||||
pub struct Request {
|
||||
/// "GET", …
|
||||
pub method: String,
|
||||
/// https://…
|
||||
pub url: String,
|
||||
/// The raw bytes.
|
||||
pub body: Vec<u8>,
|
||||
/// ("Accept", "*/*"), …
|
||||
pub headers: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
pub fn create_headers_map(headers: &[(&str, &str)]) -> BTreeMap<String, String> {
|
||||
headers
|
||||
.iter()
|
||||
.map(|e| (e.0.to_owned(), e.1.to_owned()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Create a `GET` request with the given url.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn get(url: impl ToString) -> Self {
|
||||
Self {
|
||||
method: "GET".to_owned(),
|
||||
url: url.to_string(),
|
||||
body: vec![],
|
||||
headers: Request::create_headers_map(&[("Accept", "*/*")]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `POST` request with the given url and body.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn post(url: impl ToString, body: impl ToString) -> Self {
|
||||
Self {
|
||||
method: "POST".to_owned(),
|
||||
url: url.to_string(),
|
||||
body: body.to_string().into_bytes(),
|
||||
headers: Request::create_headers_map(&[
|
||||
("Accept", "*/*"),
|
||||
("Content-Type", "text/plain; charset=utf-8"),
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Response from a completed HTTP request.
|
||||
pub struct Response {
|
||||
/// The URL we ended up at. This can differ from the request url when we have followed redirects.
|
||||
pub url: String,
|
||||
/// Did we get a 2xx response code?
|
||||
pub ok: bool,
|
||||
/// Status code (e.g. `404` for "File not found").
|
||||
pub status: u16,
|
||||
/// Status text (e.g. "File not found" for status code `404`).
|
||||
pub status_text: String,
|
||||
/// The raw bytes.
|
||||
pub bytes: Vec<u8>,
|
||||
|
||||
pub headers: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
impl Response {
|
||||
pub fn text(&self) -> Option<String> {
|
||||
String::from_utf8(self.bytes.clone()).ok()
|
||||
}
|
||||
|
||||
pub fn content_type(&self) -> Option<String> {
|
||||
self.headers.get("content-type").cloned()
|
||||
}
|
||||
}
|
||||
/// Possible errors does NOT include e.g. 404, which is NOT considered an error.
|
||||
pub type Error = String;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// You only need to look here if you are writing a backend for `epi`.
|
||||
pub mod backend {
|
||||
use super::*;
|
||||
|
||||
/// Implements `Http` requests.
|
||||
#[cfg(feature = "http")]
|
||||
pub trait Http {
|
||||
/// Calls the given callback when done.
|
||||
fn fetch_dyn(
|
||||
&self,
|
||||
request: http::Request,
|
||||
on_done: Box<dyn FnOnce(Result<http::Response, http::Error>) + Send>,
|
||||
);
|
||||
}
|
||||
|
||||
/// The data required by [`Frame`] each frame.
|
||||
pub struct FrameBuilder<'a> {
|
||||
/// Information about the integration.
|
||||
pub info: IntegrationInfo,
|
||||
/// A way to allocate textures (on integrations that support it).
|
||||
pub tex_allocator: &'a mut dyn TextureAllocator,
|
||||
/// Do http requests.
|
||||
#[cfg(feature = "http")]
|
||||
pub http: std::sync::Arc<dyn backend::Http>,
|
||||
/// Where the app can issue commands back to the integration.
|
||||
pub output: &'a mut AppOutput,
|
||||
/// If you need to request a repaint from another thread, clone this and send it to that other thread.
|
||||
|
|
Loading…
Reference in a new issue