1use std::io::{self, Read, Write};
10use std::net::TcpStream;
11
12#[derive(Debug)]
14pub struct Request {
15 pub method: String,
17 pub path: String,
19 pub query: String,
21 pub headers: Vec<(String, String)>,
23 pub body: Vec<u8>,
25}
26
27impl Request {
28 #[must_use]
30 pub fn header(&self, name: &str) -> Option<&str> {
31 self.headers
32 .iter()
33 .find(|(named, _)| named.eq_ignore_ascii_case(name))
34 .map(|(_, value)| value.as_str())
35 }
36
37 #[must_use]
39 pub fn cookie(&self, name: &str) -> Option<&str> {
40 self.headers
41 .iter()
42 .filter(|(named, _)| named == "cookie")
43 .flat_map(|(_, value)| value.split(';'))
44 .filter_map(|pair| pair.trim().split_once('='))
45 .find(|(named, _)| *named == name)
46 .map(|(_, value)| value)
47 }
48
49 #[must_use]
51 pub fn parameter(&self, name: &str) -> Option<String> {
52 url::form_urlencoded::parse(self.query.as_bytes())
53 .find(|(named, _)| named == name)
54 .map(|(_, value)| value.into_owned())
55 }
56}
57
58fn refused(words: &'static str) -> io::Error {
59 io::Error::new(io::ErrorKind::InvalidData, words)
60}
61
62pub fn read(input: &mut impl Read, head_cap: usize, body_cap: usize) -> io::Result<Request> {
70 let mut head = Vec::new();
71 let mut byte = [0_u8; 1];
72 while !head.ends_with(b"\r\n\r\n") {
73 if head.len() >= head_cap {
74 return Err(refused("the request head is past its bound"));
75 }
76 input.read_exact(&mut byte)?;
77 head.push(byte[0]);
78 }
79 let text = std::str::from_utf8(&head).map_err(|_| refused("the request head is not UTF-8"))?;
80 let mut lines = text.split("\r\n");
81 let start = lines
82 .next()
83 .ok_or_else(|| refused("the request has no line"))?;
84 let mut parts = start.split(' ');
85 let (Some(method), Some(target), Some("HTTP/1.1" | "HTTP/1.0"), None) =
86 (parts.next(), parts.next(), parts.next(), parts.next())
87 else {
88 return Err(refused("the request line is malformed"));
89 };
90 let (path, query) = target.split_once('?').unwrap_or((target, ""));
91 let mut headers = Vec::new();
92 for line in lines.filter(|line| !line.is_empty()) {
93 let (name, value) = line
94 .split_once(':')
95 .ok_or_else(|| refused("a header is malformed"))?;
96 headers.push((name.trim().to_ascii_lowercase(), value.trim().to_owned()));
97 }
98 let mut request = Request {
99 method: method.to_owned(),
100 path: path.to_owned(),
101 query: query.to_owned(),
102 headers,
103 body: Vec::new(),
104 };
105 if request.header("transfer-encoding").is_some() {
106 return Err(refused("a chunked body is not read; send Content-Length"));
107 }
108 let length = match request.header("content-length") {
109 Some(length) => length
110 .parse::<usize>()
111 .map_err(|_| refused("Content-Length is not a number"))?,
112 None => 0,
113 };
114 if length > body_cap {
115 return Err(refused("the request body is past its bound"));
116 }
117 request.body = vec![0; length];
118 input.read_exact(&mut request.body)?;
119 Ok(request)
120}
121
122type Streamed = Box<dyn FnOnce(&mut dyn Write, Option<&TcpStream>) -> io::Result<()> + Send>;
124
125enum Body {
127 Whole(Vec<u8>),
129 Stream(Streamed),
132}
133
134impl std::fmt::Debug for Body {
135 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136 match self {
137 Self::Whole(bytes) => write!(formatter, "Whole({} bytes)", bytes.len()),
138 Self::Stream(_) => formatter.write_str("Stream"),
139 }
140 }
141}
142
143#[derive(Debug)]
145pub struct Response {
146 status: u16,
147 reason: &'static str,
148 headers: Vec<(&'static str, String)>,
149 cache: &'static str,
151 body: Body,
152}
153
154impl Response {
155 #[must_use]
157 pub fn new(status: u16, content_type: &'static str, body: Vec<u8>) -> Self {
158 let reason = match status {
159 200 => "OK",
160 204 => "No Content",
161 303 => "See Other",
162 400 => "Bad Request",
163 401 => "Unauthorized",
164 403 => "Forbidden",
165 404 => "Not Found",
166 405 => "Method Not Allowed",
167 502 => "Bad Gateway",
168 503 => "Service Unavailable",
169 _ => "Status",
170 };
171 Self {
172 status,
173 reason,
174 headers: vec![("Content-Type", content_type.to_owned())],
175 cache: "no-store",
176 body: Body::Whole(body),
177 }
178 }
179
180 #[must_use]
183 pub fn stream(
184 content_type: &'static str,
185 body: impl FnOnce(&mut dyn Write) -> io::Result<()> + Send + 'static,
186 ) -> Self {
187 Self::stream_with_peer(content_type, move |output, _peer| body(output))
188 }
189
190 pub(crate) fn stream_with_peer(
191 content_type: &'static str,
192 body: impl FnOnce(&mut dyn Write, Option<&TcpStream>) -> io::Result<()> + Send + 'static,
193 ) -> Self {
194 let mut response = Self::new(200, content_type, Vec::new());
195 response.body = Body::Stream(Box::new(body));
196 response
197 }
198
199 #[must_use]
202 pub const fn cached(mut self, policy: &'static str) -> Self {
203 self.cache = policy;
204 self
205 }
206
207 #[must_use]
209 pub fn json(status: u16, value: &serde_json::Value) -> Self {
210 Self::new(status, "application/json", value.to_string().into_bytes())
211 }
212
213 #[must_use]
215 pub fn no_content() -> Self {
216 let mut response = Self::new(204, "text/plain; charset=utf-8", Vec::new());
217 response.headers.clear();
218 response
219 }
220
221 #[must_use]
223 pub fn redirect(location: &str) -> Self {
224 Self::new(303, "text/plain; charset=utf-8", Vec::new()).with("Location", location)
225 }
226
227 #[must_use]
229 pub fn with(mut self, name: &'static str, value: &str) -> Self {
230 self.headers.push((name, value.to_owned()));
231 self
232 }
233
234 pub fn write(self, output: &mut impl Write) -> io::Result<()> {
242 self.write_with_peer(output, None)
243 }
244
245 pub(super) fn write_to(self, stream: &TcpStream) -> io::Result<()> {
246 self.write_with_peer(&mut &*stream, Some(stream))
247 }
248
249 fn write_with_peer(self, output: &mut impl Write, peer: Option<&TcpStream>) -> io::Result<()> {
250 if self
251 .headers
252 .iter()
253 .any(|(_, value)| value.contains(['\r', '\n']))
254 {
255 return Err(refused("a header value holds a line break"));
256 }
257 let mut head = format!("HTTP/1.1 {} {}\r\n", self.status, self.reason);
258 for (name, value) in &self.headers {
259 head.push_str(name);
260 head.push_str(": ");
261 head.push_str(value);
262 head.push_str("\r\n");
263 }
264 if let Body::Whole(body) = &self.body
266 && self.status != 204
267 {
268 head.push_str("Content-Length: ");
269 head.push_str(&body.len().to_string());
270 head.push_str("\r\n");
271 }
272 head.push_str("Cache-Control: ");
273 head.push_str(self.cache);
274 head.push_str("\r\nConnection: close\r\n\r\n");
275 let mut bytes = head.into_bytes();
276 match self.body {
277 Body::Whole(body) => {
278 bytes.extend_from_slice(&body);
279 output.write_all(&bytes)?;
280 output.flush()
281 }
282 Body::Stream(body) => {
283 output.write_all(&bytes)?;
284 output.flush()?;
285 body(&mut *output, peer)?;
286 output.flush()
287 }
288 }
289 }
290}
291
292#[cfg(test)]
293#[path = "http_tests.rs"]
294mod tests;