ehttp 0.2.0

Minimal HTTP client for both native and WASM
Documentation
use std::collections::BTreeMap;

use crate::{Request, Response};

/// Only available when compiling for native.
///
/// NOTE: Ok(…) is returned on network error.
/// Err is only for failure to use the fetch api.
pub fn fetch_blocking(request: &Request) -> crate::Result<Response> {
    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) fn fetch(request: Request, on_done: Box<dyn FnOnce(crate::Result<Response>) + Send>) {
    std::thread::spawn(move || {
        let result = fetch_blocking(&request);
        on_done(result)
    });
}