//! A scripted HTTP server for tests: it records each request and answers with what the test's //! function returns for it. Built on `tls_server`. Do not edit. #![allow(dead_code)] // each test file uses a different part of this module use std::io::{Read, Write}; use std::net::SocketAddr; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex, mpsc}; use rustls::ServerConfig; use crate::tls_server::serve; #[derive(Debug, Clone)] pub struct Request { pub method: String, pub path: String, /// The whole head, for tests that look for a header. pub head: String, pub body: Vec, } impl Request { pub fn json(&self) -> serde_json::Value { serde_json::from_slice(&self.body).unwrap() } } /// A response with a JSON body. pub fn reply(status: u16, extra_headers: &str, body: &str) -> String { format!( "HTTP/1.1 {status} X\r\nContent-Type: application/json\r\n{extra_headers}Content-Length: {}\r\n\r\n{body}", body.len() ) } fn read_request(conn: &mut dyn Read) -> Option { let mut head = Vec::new(); let mut byte = [0u8; 1]; while !head.ends_with(b"\r\n\r\n") { if conn.read(&mut byte).ok()? == 0 { return None; } head.push(byte[0]); } let head = String::from_utf8(head).ok()?; let mut first = head.split_whitespace(); let method = first.next()?.to_string(); let path = first.next()?.to_string(); let length = head .lines() .find_map(|l| { l.to_ascii_lowercase() .strip_prefix("content-length:") .map(|v| v.trim().parse().ok()) }) .flatten() .unwrap_or(0); let mut body = vec![0u8; length]; conn.read_exact(&mut body).ok()?; Some(Request { method, path, head, body, }) } /// Serve requests: `answer(n, &request)` gives the response to the n-th request (from 0). Every /// request is sent on the returned channel. pub fn serve_http( tls: Option>, answer: F, ) -> (SocketAddr, mpsc::Receiver) where F: Fn(usize, &Request) -> String + Send + Sync + 'static, { let (tx, rx) = mpsc::channel(); let tx = Mutex::new(tx); let count = AtomicUsize::new(0); let addr = serve(tls, move |mut conn| { let Some(request) = read_request(&mut conn) else { return; }; let n = count.fetch_add(1, Ordering::SeqCst); let response = answer(n, &request); let _ = tx.lock().unwrap().send(request); let _ = conn.write_all(response.as_bytes()); let _ = conn.flush(); }); (addr, rx) }