mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 21:00:03 -04:00
Generalize http fetch (#488)
* Generalize http fetch - allow bytes as request body - expose request and response headers in API - update http example to show response headers and allow POST requests * clippy fixes * add missing comment, pub * doc comment fix * fix: missing argument when feature syntect not enabled * formatting fixes Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> * remove commented out code Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> * formatting fixes Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> * cargo fmt Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
use epi::http::Response;
|
||||
use epi::http::{Request, Response};
|
||||
use std::sync::mpsc::Receiver;
|
||||
|
||||
struct Resource {
|
||||
/// HTTP response
|
||||
response: Response,
|
||||
|
||||
text: Option<String>,
|
||||
|
||||
/// If set, the response was an image.
|
||||
image: Option<Image>,
|
||||
|
||||
@@ -14,26 +16,43 @@ struct Resource {
|
||||
|
||||
impl Resource {
|
||||
fn from_response(response: Response) -> Self {
|
||||
let image = if response.header_content_type.starts_with("image/") {
|
||||
let content_type = response.content_type().unwrap_or_default();
|
||||
let image = if content_type.starts_with("image/") {
|
||||
Image::decode(&response.bytes)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let colored_text = syntax_highlighting(&response);
|
||||
let text = response.text();
|
||||
|
||||
let colored_text = text
|
||||
.as_ref()
|
||||
.and_then(|text| syntax_highlighting(&response, text));
|
||||
|
||||
Self {
|
||||
response,
|
||||
text,
|
||||
image,
|
||||
colored_text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
enum Method {
|
||||
Get,
|
||||
Post,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct HttpApp {
|
||||
url: String,
|
||||
|
||||
method: Method,
|
||||
|
||||
request_body: String,
|
||||
|
||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||
in_progress: Option<Receiver<Result<Response, String>>>,
|
||||
|
||||
@@ -48,6 +67,8 @@ impl Default for HttpApp {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
url: "https://raw.githubusercontent.com/emilk/egui/master/README.md".to_owned(),
|
||||
method: Method::Get,
|
||||
request_body: r#"["posting some json", { "more_json" : true }]"#.to_owned(),
|
||||
in_progress: Default::default(),
|
||||
result: Default::default(),
|
||||
tex_mngr: Default::default(),
|
||||
@@ -78,12 +99,18 @@ impl epi::App for HttpApp {
|
||||
"(source code)"
|
||||
));
|
||||
|
||||
if let Some(url) = ui_url(ui, frame, &mut self.url) {
|
||||
if let Some(request) = ui_url(
|
||||
ui,
|
||||
frame,
|
||||
&mut self.url,
|
||||
&mut self.method,
|
||||
&mut self.request_body,
|
||||
) {
|
||||
let repaint_signal = frame.repaint_signal();
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
self.in_progress = Some(receiver);
|
||||
|
||||
frame.http_fetch(epi::http::Request::get(url), move |response| {
|
||||
frame.http_fetch(request, move |response| {
|
||||
sender.send(response).ok();
|
||||
repaint_signal.request_repaint();
|
||||
});
|
||||
@@ -100,7 +127,10 @@ impl epi::App for HttpApp {
|
||||
}
|
||||
Err(error) => {
|
||||
// This should only happen if the fetch API isn't available or something similar.
|
||||
ui.add(egui::Label::new(error).text_color(egui::Color32::RED));
|
||||
ui.add(
|
||||
egui::Label::new(if error.is_empty() { "Error" } else { error })
|
||||
.text_color(egui::Color32::RED),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,13 +138,38 @@ impl epi::App for HttpApp {
|
||||
}
|
||||
}
|
||||
|
||||
fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame<'_>, url: &mut String) -> Option<String> {
|
||||
fn ui_url(
|
||||
ui: &mut egui::Ui,
|
||||
frame: &mut epi::Frame<'_>,
|
||||
url: &mut String,
|
||||
method: &mut Method,
|
||||
request_body: &mut String,
|
||||
) -> Option<Request> {
|
||||
let mut trigger_fetch = false;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
egui::Grid::new("request_params").show(ui, |ui| {
|
||||
ui.label("URL:");
|
||||
trigger_fetch |= ui.text_edit_singleline(url).lost_focus();
|
||||
trigger_fetch |= ui.button("GET").clicked();
|
||||
ui.horizontal(|ui| {
|
||||
trigger_fetch |= ui.text_edit_singleline(url).lost_focus();
|
||||
egui::ComboBox::from_id_source("method")
|
||||
.selected_text(format!("{:?}", method))
|
||||
.width(60.0)
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(method, Method::Get, "GET");
|
||||
ui.selectable_value(method, Method::Post, "POST");
|
||||
});
|
||||
trigger_fetch |= ui.button("▶").clicked();
|
||||
});
|
||||
ui.end_row();
|
||||
if *method == Method::Post {
|
||||
ui.label("Body:");
|
||||
ui.add(
|
||||
egui::TextEdit::multiline(request_body)
|
||||
.code_editor()
|
||||
.desired_rows(1),
|
||||
);
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
|
||||
if frame.is_web() {
|
||||
@@ -127,6 +182,7 @@ fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame<'_>, url: &mut String) -> Op
|
||||
"https://raw.githubusercontent.com/emilk/egui/master/{}",
|
||||
file!()
|
||||
);
|
||||
*method = Method::Get;
|
||||
trigger_fetch = true;
|
||||
}
|
||||
if ui.button("Random image").clicked() {
|
||||
@@ -134,12 +190,21 @@ fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame<'_>, url: &mut String) -> Op
|
||||
let width = 640;
|
||||
let height = 480;
|
||||
*url = format!("https://picsum.photos/seed/{}/{}/{}", seed, width, height);
|
||||
*method = Method::Get;
|
||||
trigger_fetch = true;
|
||||
}
|
||||
if ui.button("Post to httpbin.org").clicked() {
|
||||
*url = "https://httpbin.org/post".to_owned();
|
||||
*method = Method::Post;
|
||||
trigger_fetch = true;
|
||||
}
|
||||
});
|
||||
|
||||
if trigger_fetch {
|
||||
Some(url.clone())
|
||||
Some(match *method {
|
||||
Method::Get => Request::get(url),
|
||||
Method::Post => Request::post(url, request_body),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -153,6 +218,7 @@ fn ui_resource(
|
||||
) {
|
||||
let Resource {
|
||||
response,
|
||||
text,
|
||||
image,
|
||||
colored_text,
|
||||
} = resource;
|
||||
@@ -162,13 +228,34 @@ fn ui_resource(
|
||||
"status: {} ({})",
|
||||
response.status, response.status_text
|
||||
));
|
||||
ui.monospace(format!("Content-Type: {}", response.header_content_type));
|
||||
ui.monospace(format!(
|
||||
"Size: {:.1} kB",
|
||||
"content-type: {}",
|
||||
response.content_type().unwrap_or_default()
|
||||
));
|
||||
ui.monospace(format!(
|
||||
"size: {:.1} kB",
|
||||
response.bytes.len() as f32 / 1000.0
|
||||
));
|
||||
|
||||
if let Some(text) = &response.text {
|
||||
ui.separator();
|
||||
|
||||
egui::CollapsingHeader::new("Response headers")
|
||||
.default_open(false)
|
||||
.show(ui, |ui| {
|
||||
egui::Grid::new("response_headers")
|
||||
.spacing(egui::vec2(ui.spacing().item_spacing.x * 2.0, 0.0))
|
||||
.show(ui, |ui| {
|
||||
for header in &response.headers {
|
||||
ui.label(header.0);
|
||||
ui.label(header.1);
|
||||
ui.end_row();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
if let Some(text) = &text {
|
||||
let tooltip = "Click to copy the response body";
|
||||
if ui.button("📋").on_hover_text(tooltip).clicked() {
|
||||
ui.output().copied_text = text.clone();
|
||||
@@ -185,7 +272,7 @@ fn ui_resource(
|
||||
}
|
||||
} else if let Some(colored_text) = colored_text {
|
||||
colored_text.ui(ui);
|
||||
} else if let Some(text) = &response.text {
|
||||
} else if let Some(text) = &text {
|
||||
ui.monospace(text);
|
||||
} else {
|
||||
ui.monospace("[binary]");
|
||||
@@ -197,8 +284,7 @@ fn ui_resource(
|
||||
// Syntax highlighting:
|
||||
|
||||
#[cfg(feature = "syntect")]
|
||||
fn syntax_highlighting(response: &Response) -> Option<ColoredText> {
|
||||
let text = response.text.as_ref()?;
|
||||
fn syntax_highlighting(response: &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)
|
||||
@@ -253,7 +339,7 @@ impl ColoredText {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "syntect"))]
|
||||
fn syntax_highlighting(_: &Response) -> Option<ColoredText> {
|
||||
fn syntax_highlighting(_: &Response, _: &str) -> Option<ColoredText> {
|
||||
None
|
||||
}
|
||||
#[cfg(not(feature = "syntect"))]
|
||||
|
||||
Reference in New Issue
Block a user