use std::sync::atomic::{AtomicU8, Ordering};
use super::http::{self, Request};
use super::{Shared, current};
use std::net::TcpStream;
static FAULT: AtomicU8 = AtomicU8::new(0);
const OFF: u8 = 0;
const BAD_GATEWAY: u8 = 1;
const SLOW: u8 = 2;
const HTML: u8 = 3;
const EMPTY: u8 = 4;
const CLOUDFLARE_502: &str = concat!(
"<!DOCTYPE html><html><head><title>trycloudflare.com | 502: Bad gateway",
"</title></head><body><h1>Bad gateway</h1><span>Error code 502</span>",
"<div>Visit cloudflare.com for more information.</div></body></html>"
);
pub fn intercept(stream: &mut TcpStream, request: &Request) -> bool {
if !request.path.starts_with("/api/") || request.path.starts_with("/api/debug/") {
return false;
}
match FAULT.load(Ordering::Relaxed) {
OFF => false,
BAD_GATEWAY => {
http::respond(
stream,
Some(request),
502,
"text/html; charset=utf-8",
CLOUDFLARE_502.as_bytes(),
);
true
}
SLOW => {
std::thread::sleep(std::time::Duration::from_secs(30));
false
}
HTML => {
http::respond(
stream,
Some(request),
200,
"text/html; charset=utf-8",
b"<html><body>not the json you asked for</body></html>",
);
true
}
EMPTY => {
http::respond(
stream,
Some(request),
200,
"application/json; charset=utf-8",
b"",
);
true
}
_ => false,
}
}
pub fn route(shared: &Shared, stream: &mut TcpStream, request: &Request, rest: &str) -> bool {
match rest {
"state" => {
let snapshot = current(shared);
let body = serde_json::json!({
"snapshot_version": snapshot.version,
"sessions": snapshot.sessions.len(),
"running": snapshot.sessions.iter().filter(|s| s.is_running()).count(),
"host_errors": snapshot.host_errors,
"json_bytes": snapshot.json.len(),
"actions": shared.actions,
"tokenless": shared.token.is_empty(),
"plan": format!("{:?}", shared.plan),
"fault": fault_name(FAULT.load(Ordering::Relaxed)),
});
json(stream, request, &body);
true
}
"why" => {
let snapshot = current(shared);
let rows: Vec<_> = snapshot
.sessions
.iter()
.map(|s| {
serde_json::json!({
"key": s.key(),
"running": s.is_running(),
"has_process": s.process.is_some(),
"launched_as": s.launched_as(),
"forked": s.launched_as() != s.session_id,
"last_active": s.last_active,
"cwd": s.label_source,
})
})
.collect();
json(stream, request, &serde_json::json!({ "sessions": rows }));
true
}
"fault" => {
let mode = request
.query
.get("mode")
.map_or_else(|| "off".to_string(), |m| m.to_lowercase());
let armed = match mode.as_str() {
"off" | "none" => OFF,
"502" | "gateway" => BAD_GATEWAY,
"slow" => SLOW,
"html" => HTML,
"empty" => EMPTY,
_ => {
http::respond_error(
stream,
Some(request),
400,
"mode must be one of: off, 502, slow, html, empty",
);
return true;
}
};
FAULT.store(armed, Ordering::Relaxed);
json(
stream,
request,
&serde_json::json!({ "fault": fault_name(armed) }),
);
true
}
_ => false,
}
}
fn fault_name(mode: u8) -> &'static str {
match mode {
BAD_GATEWAY => "502",
SLOW => "slow",
HTML => "html",
EMPTY => "empty",
_ => "off",
}
}
fn json(stream: &mut TcpStream, request: &Request, body: &serde_json::Value) {
http::respond(
stream,
Some(request),
200,
"application/json; charset=utf-8",
body.to_string().as_bytes(),
);
}