host.rsannotatedhost.rssource64 lines · 2.6 KB · raw

One host under test, reached over HTTP: the same client for whiskersd, wrangler dev and celld dev.

3use std::time::{Duration, Instant};

The header that names a household in a host that was told to trust it (.dev.vars).

6pub const HOUSEHOLD_HEADER: &str = "x-whiskers-household";

What one request got back, as far as a device could tell.

9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct Reply {
11    pub status: u16,
12    pub content_type: Option<String>,
13    pub body: String,
14}

A host's address and how to ask it things.

17#[derive(Clone)]
18pub struct Host {
19    base: String,
20    agent: ureq::Agent,
21}
23impl Host {
24    pub fn new(base: &str) -> Self {
25        let agent: ureq::Agent = ureq::Agent::config_builder()
26            .http_status_as_error(false)
27            .proxy(None)
28            .timeout_global(Some(Duration::from_secs(60)))
29            .build()
30            .into();
31        Self { base: base.trim_end_matches('/').to_owned(), agent }
32    }

A request. method is any; household is sent as the household header.

35    pub fn send(&self, method: &str, path: &str, household: &str, body: &str) -> Result<Reply, String> {
36        let url = format!("{}{path}", self.base);
37        let request = ureq::http::Request::builder().method(method).uri(&url).header(HOUSEHOLD_HEADER, household);
38        let request = if method == "POST" { request.body(body.to_owned()) } else { request.body(String::new()) }.map_err(|e| e.to_string())?;
39        let mut response = self.agent.run(request).map_err(|e| format!("{method} {path}: {e}"))?;
40        let status = response.status().as_u16();
41        let content_type = response.headers().get("content-type").and_then(|v| v.to_str().ok()).map(str::to_owned);
42        let body = response.body_mut().read_to_string().map_err(|e| format!("{method} {path}: reading the body: {e}"))?;
43        Ok(Reply { status, content_type, body })
44    }
46    pub fn post(&self, path: &str, household: &str, body: &str) -> Result<Reply, String> {
47        self.send("POST", path, household, body)
48    }

Waits until the host answers a sync, for after a restart.

51    pub fn wait_ready(&self, within: Duration) -> Result<Duration, String> {
52        let started = Instant::now();
53        loop {
54            let probe = self.post("/memory/sync", "readiness-probe", r#"{"facts":[],"forgotten":[]}"#);
55            if matches!(probe, Ok(Reply { status: 200, .. })) {
56                return Ok(started.elapsed());
57            }
58            if started.elapsed() > within {
59                return Err(format!("the host was not ready within {within:?}: {probe:?}"));
60            }
61            std::thread::sleep(Duration::from_millis(200));
62        }
63    }
64}