Skip to main content

cherenkov/server/
http.rs

1//! Bounded HTTP input and JSON/SSE wire framing.
2
3use crate::units::BYTES_PER_KIB;
4use anyhow::{Context, Result, ensure};
5use serde_json::{Value, json};
6use std::io::{BufRead, BufReader, Read, Write};
7use std::net::TcpStream;
8
9fn line(reader: &mut impl BufRead, remaining: &mut usize) -> Result<String> {
10    let mut bytes = Vec::new();
11    let n = reader
12        .take((*remaining + 1) as u64)
13        .read_until(b'\n', &mut bytes)?;
14
15    ensure!(
16        n > 0 && n <= *remaining && bytes.ends_with(b"\n"),
17        "invalid or oversized HTTP headers"
18    );
19
20    *remaining -= n;
21
22    Ok(String::from_utf8(bytes)?
23        .trim_end_matches(['\r', '\n'])
24        .to_owned())
25}
26
27pub(super) fn read_http(
28    stream: &mut TcpStream,
29    max_body: usize,
30) -> Result<(String, String, Vec<u8>)> {
31    let mut reader = BufReader::new(stream.try_clone()?);
32    let mut remaining = 16 * BYTES_PER_KIB;
33    let first = line(&mut reader, &mut remaining)?;
34    let parts: Vec<_> = first.split_whitespace().collect();
35
36    ensure!(
37        parts.len() == 3 && parts[2].starts_with("HTTP/1."),
38        "invalid request line"
39    );
40
41    let mut length = None;
42    let mut expect = false;
43
44    loop {
45        let h = line(&mut reader, &mut remaining)?;
46
47        if h.is_empty() {
48            break;
49        }
50
51        let (key, value) = h.split_once(':').context("invalid header")?;
52
53        match key.to_ascii_lowercase().as_str() {
54            "content-length" => {
55                ensure!(length.is_none(), "duplicate Content-Length");
56
57                length = Some(
58                    value
59                        .trim()
60                        .parse::<usize>()
61                        .context("invalid Content-Length")?,
62                );
63            }
64            "transfer-encoding" => {
65                anyhow::bail!("Transfer-Encoding is unsupported; send Content-Length")
66            }
67            "expect" => {
68                ensure!(
69                    value.trim().eq_ignore_ascii_case("100-continue"),
70                    "unsupported Expect"
71                );
72
73                expect = true;
74            }
75            _ => {}
76        }
77    }
78
79    let length = length.unwrap_or(0);
80
81    ensure!(
82        length <= max_body,
83        "request body exceeds server limit of {max_body} bytes"
84    );
85
86    if expect {
87        stream.write_all(b"HTTP/1.1 100 Continue\r\n\r\n")?;
88        stream.flush()?;
89    }
90
91    let mut body = vec![0; length];
92
93    reader.read_exact(&mut body)?;
94
95    Ok((parts[0].to_owned(), parts[1].to_owned(), body))
96}
97
98pub(super) fn respond(out: &mut impl Write, status: u16, body: &Value) -> Result<()> {
99    let bytes = serde_json::to_vec(body)?;
100    let reason = match status {
101        200 => "OK",
102        400 => "Bad Request",
103        404 => "Not Found",
104        409 => "Conflict",
105        499 => "Client Closed Request",
106        503 => "Service Unavailable",
107        _ => "Internal Server Error",
108    };
109
110    write!(
111        out,
112        "HTTP/1.1 {status} {reason}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
113        bytes.len()
114    )?;
115    out.write_all(&bytes)?;
116    out.flush()?;
117
118    Ok(())
119}
120
121pub(super) fn error(out: &mut TcpStream, status: u16, message: &str) -> Result<()> {
122    respond(
123        out,
124        status,
125        &json!({"error":{"message":message,"type":if status >= 500 {"server_error"} else {"invalid_request_error"},"param":null,"code":null}}),
126    )
127}
128
129pub(super) fn sse(out: &mut impl Write, body: &Value) -> Result<()> {
130    writeln!(out, "data: {}\n", serde_json::to_string(body)?)?;
131    out.flush()?;
132
133    Ok(())
134}
135
136#[cfg(test)]
137#[path = "../../tests/unit/server/http.rs"]
138mod tests;