smb_server_test_dashboard/
http.rs1use crate::{csv, json};
5use std::io::{BufRead, BufReader, Read, Write};
6use std::net::TcpStream;
7use std::path::Path;
8
9const INDEX_HTML: &str = include_str!("index.html");
11
12const HTTP_OK: u16 = 200;
14const HTTP_NOT_FOUND: u16 = 404;
15const HTTP_METHOD_NOT_ALLOWED: u16 = 405;
16
17pub fn handle_connection(stream: TcpStream, csv_path: &Path) {
19 let mut reader = BufReader::new(stream);
20 let Some((method, target)) = read_request_line(&mut reader) else {
21 return;
22 };
23 drain_headers(&mut reader);
24 let stream = reader.get_mut();
25
26 if method != "GET" {
27 respond(stream, HTTP_METHOD_NOT_ALLOWED, "text/plain", b"method not allowed");
28 return;
29 }
30 route(stream, &target, csv_path);
31}
32
33fn read_request_line(reader: &mut BufReader<TcpStream>) -> Option<(String, String)> {
34 let mut line = String::new();
35 if reader.read_line(&mut line).ok()? == 0 {
36 return None;
37 }
38 let mut parts = line.split_whitespace();
39 let method = parts.next()?.to_string();
40 let target = parts.next()?.to_string();
41 Some((method, target))
42}
43
44fn drain_headers(reader: &mut BufReader<TcpStream>) {
46 let mut line = String::new();
47 while let Ok(n) = reader.read_line(&mut line) {
48 if n == 0 || line == "\r\n" || line == "\n" {
49 break;
50 }
51 line.clear();
52 }
53}
54
55fn route(stream: &mut TcpStream, target: &str, csv_path: &Path) {
56 let path = target.split('?').next().unwrap_or("/");
57 match path {
58 "/" | "/index.html" => respond(stream, HTTP_OK, "text/html; charset=utf-8", INDEX_HTML.as_bytes()),
59 "/api/status" => respond_json(stream, &status_json(csv_path)),
60 "/api/tests" => respond_json(stream, &tests_json(csv_path)),
61 "/healthz" => respond(stream, HTTP_OK, "text/plain", b"ok"),
62 _ => respond(stream, HTTP_NOT_FOUND, "text/plain", b"not found"),
63 }
64}
65
66fn status_json(csv_path: &Path) -> String {
67 let rows = csv::load(csv_path);
68 let summary = csv::summarize(&rows);
69 json::status_body(&rows, &summary)
70}
71
72fn tests_json(csv_path: &Path) -> String {
73 let rows = csv::load(csv_path);
74 json::tests_body(&rows)
75}
76
77fn respond_json(stream: &mut TcpStream, body: &str) {
78 respond(stream, HTTP_OK, "application/json; charset=utf-8", body.as_bytes());
79}
80
81fn respond(stream: &mut TcpStream, code: u16, content_type: &str, body: &[u8]) {
83 let reason = match code {
84 200 => "OK",
85 404 => "Not Found",
86 405 => "Method Not Allowed",
87 _ => "Error",
88 };
89 let header = format!(
90 "HTTP/1.1 {code} {reason}\r\n\
91 Content-Type: {content_type}\r\n\
92 Content-Length: {}\r\n\
93 Access-Control-Allow-Origin: *\r\n\
94 Cache-Control: no-store\r\n\
95 Connection: close\r\n\r\n",
96 body.len()
97 );
98 let _ = stream.write_all(header.as_bytes());
99 let _ = stream.write_all(body);
100 let _ = stream.flush();
101 let _ = stream.read(&mut [0u8; 0]);
103}