wireforge_app/http/
response.rs1use super::headers::HeaderIter;
4
5#[derive(Debug, Clone)]
7pub struct HttpResponse<'a> {
8 buf: &'a [u8],
9 version_end: usize,
10 status_start: usize,
11 status_end: usize,
12 reason_end: usize,
13 headers_start: usize,
14 body_start: usize,
15}
16
17impl<'a> HttpResponse<'a> {
18 pub fn parse(buf: &'a [u8]) -> Option<Self> {
19 let response_line_end = buf.windows(2).position(|w| w == b"\r\n")?;
20 let line = &buf[..response_line_end];
21
22 let sp1 = line.iter().position(|&b| b == b' ')?;
23 let sp2 = line[sp1 + 1..].iter().position(|&b| b == b' ')?;
24 let sp2 = sp1 + 1 + sp2;
25
26 let headers_start = response_line_end + 2;
27 let body_start = find_body_start(&buf[headers_start..]).map(|p| headers_start + p);
28
29 Some(Self {
30 buf,
31 version_end: sp1,
32 status_start: sp1 + 1,
33 status_end: sp2,
34 reason_end: response_line_end,
35 headers_start,
36 body_start: body_start.unwrap_or(buf.len()),
37 })
38 }
39
40 pub fn version(&self) -> &str { core::str::from_utf8(&self.buf[..self.version_end]).unwrap_or("") }
41 pub fn status_code(&self) -> u16 {
42 core::str::from_utf8(&self.buf[self.status_start..self.status_end])
43 .ok()
44 .and_then(|s| s.parse().ok())
45 .unwrap_or(0)
46 }
47 pub fn reason(&self) -> &str { core::str::from_utf8(&self.buf[self.status_end + 1..self.reason_end]).unwrap_or("") }
48 pub fn headers(&self) -> HeaderIter<'a> { HeaderIter::new(&self.buf[self.headers_start..self.body_start]) }
49 pub fn body(&self) -> &[u8] { &self.buf[self.body_start..] }
50}
51
52fn find_body_start(data: &[u8]) -> Option<usize> {
53 data.windows(4).position(|w| w == b"\r\n\r\n").map(|p| p + 4)
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59
60 #[test]
61 fn parse_200_ok() {
62 let data = b"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<body>hello</body>";
63 let resp = HttpResponse::parse(data).unwrap();
64 assert_eq!(resp.version(), "HTTP/1.1");
65 assert_eq!(resp.status_code(), 200);
66 assert_eq!(resp.reason(), "OK");
67 assert_eq!(resp.body(), b"<body>hello</body>");
68
69 let headers: Vec<_> = resp.headers().collect();
70 assert_eq!(headers.len(), 1);
71 assert_eq!(headers[0].name, "Content-Type");
72 }
73
74 #[test]
75 fn parse_404() {
76 let data = b"HTTP/1.0 404 Not Found\r\n\r\n";
77 let resp = HttpResponse::parse(data).unwrap();
78 assert_eq!(resp.status_code(), 404);
79 assert_eq!(resp.reason(), "Not Found");
80 assert_eq!(resp.body(), b"");
81 }
82
83 #[test]
84 fn parse_get_request() {
85 let data = b"GET /index.html HTTP/1.1\r\nHost: example.com\r\n\r\n";
86 let req = crate::http::HttpRequest::parse(data).unwrap();
87 assert_eq!(req.method(), "GET");
88 assert_eq!(req.uri(), "/index.html");
89 assert_eq!(req.version(), "HTTP/1.1");
90 assert_eq!(req.body(), b"");
91
92 let headers: Vec<_> = req.headers().collect();
93 assert_eq!(headers.len(), 1);
94 assert_eq!(headers[0].name, "Host");
95 }
96}