1use builders::Builder;
3mod parse;
4use core::fmt;
5use std::{
6 collections::HashMap,
7 env,
8 ffi::OsStr,
9 io::{BufRead, BufReader, Read, Write},
10 path::Path,
11};
12
13use parse::parse_request;
14
15use crate::{
16 HttpMethod, HttpResponse, HttpStream, Result, StatusCode,
17 encoding::Chunked,
18 stream::{self, IntoHttpStream},
19};
20
21#[derive(Builder)]
25pub struct HttpRequest {
26 #[builder(def = { HttpMethod::GET })]
27 method: HttpMethod,
28 url: Box<str>,
29 #[builder(map = "header")]
30 headers: HashMap<Box<str>, Box<str>>,
31 #[builder(map = "param")]
32 params: HashMap<Box<str>, Box<str>>,
33 #[builder(map = "response_header")]
34 response_headers: HashMap<Box<str>, Box<str>>,
35 #[builder(def = 1.0)]
36 version: f32,
37 #[builder(def = { BufReader::new(stream::dummy())} )]
38 stream: BufReader<Box<dyn HttpStream>>,
39 #[builder(def = 200u16)]
40 status: u16,
41 #[builder(optional = true)]
42 body: Option<Box<[u8]>>,
43}
44
45impl fmt::Debug for HttpRequest {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 f.debug_struct("HttpRequest")
48 .field("method", &self.method)
49 .field("url", &self.url)
50 .field("headers", &self.headers)
51 .field("params", &self.params)
52 .field("response_headers", &self.response_headers)
53 .field("version", &self.version)
54 .field("status", &self.status)
55 .field("body", &self.body)
56 .finish()
57 }
58}
59
60impl HttpRequest {
61 pub fn parse<S: IntoHttpStream>(stream: S) -> Result<Self> {
63 let stream: Box<dyn HttpStream> = Box::new(stream.into_http_stream());
64 parse_request(BufReader::new(stream))
65 }
66
67 #[inline]
68 pub fn keep_alive(self) -> Result<Self> {
69 let mut req = parse_request(self.stream)?;
70 req.set_header("Connection", "keep-alive");
71 Ok(req)
72 }
73
74 #[inline]
75 pub fn stream(&self) -> &BufReader<Box<dyn HttpStream>> {
76 &self.stream
77 }
78
79 #[inline]
80 pub fn stream_mut(&mut self) -> &mut BufReader<Box<dyn HttpStream>> {
81 &mut self.stream
82 }
83
84 #[inline]
86 pub fn url(&self) -> &str {
87 &self.url
88 }
89
90 #[inline]
91 pub fn set_url(&mut self, url: impl Into<Box<str>>) {
92 self.url = url.into();
93 }
94 #[inline]
96 #[must_use]
97 pub fn params(&self) -> &HashMap<Box<str>, Box<str>> {
98 &self.params
99 }
100
101 #[inline]
102 #[must_use]
103 pub fn param(&self, key: &str) -> Option<&str> {
104 self.params.get(key).map(AsRef::as_ref)
105 }
106
107 pub fn filename(&self) -> Result<Box<str>> {
113 let mut cwd = env::current_dir()?;
114 cwd.push(Path::new(OsStr::new(&self.url[1..])));
115 let cwd = cwd.to_str().ok_or("Error getting cwd")?;
116 Ok(Box::from(cwd))
117 }
118
119 pub fn write_to(&self, f: &mut dyn Write) -> Result<()> {
121 write!(f, "{} {}", self.method(), self.url())?;
122 if !self.params().is_empty() {
123 write!(f, "?")?;
124 for (k, v) in self.params() {
125 let ke = url::encode(k).unwrap_or("".into());
126 let ve = url::encode(v).unwrap_or("".into());
127 write!(f, "{ke}={ve}&")?;
128 }
129 }
130 write!(f, " HTTP/{}\r\n", self.version())?;
131
132 for (k, v) in self.headers() {
133 write!(f, "{k}: {v}\r\n")?;
134 }
135
136 if let Some(ref b) = self.body {
137 f.write_all(b)?;
138 }
139
140 write!(f, "\r\n")?;
141
142 Ok(())
143 }
144
145 pub fn send_to<Out: IntoHttpStream>(&self, stream: Out) -> crate::Result<HttpResponse> {
150 let mut stream = stream.into_http_stream();
151 self.write_to(&mut stream)?;
152 stream.flush()?;
153 HttpResponse::parse(stream)
154 }
155 #[inline]
156 #[must_use]
157 pub fn method(&self) -> &HttpMethod {
158 &self.method
159 }
160
161 #[inline]
162 #[must_use]
163 pub fn status(&self) -> u16 {
164 self.status
165 }
166
167 #[inline]
168 pub fn set_status(&mut self, status: u16) -> &mut Self {
169 self.status = status;
170 self
171 }
172
173 #[inline]
174 #[must_use]
175 pub fn version(&self) -> f32 {
176 self.version
177 }
178
179 #[inline]
184 #[must_use]
185 pub fn content_length(&self) -> usize {
186 match self.headers.get("Content-Length") {
187 Some(l) => l.parse().unwrap_or(0),
188 None => 0,
189 }
190 }
191
192 #[inline]
194 #[must_use]
195 pub fn header(&self, key: &str) -> Option<&str> {
196 self.headers.get(key).map(AsRef::as_ref)
197 }
198
199 #[inline]
200 #[must_use]
201 pub fn headers(&self) -> &HashMap<Box<str>, Box<str>> {
202 &self.headers
203 }
204
205 #[inline]
206 pub fn set_header(&mut self, key: impl Into<Box<str>>, value: impl Into<Box<str>>) {
207 self.response_headers.insert(key.into(), value.into());
208 }
209
210 pub(crate) fn read_body_into_buffer(&mut self) -> Result<()> {
216 let len = self.content_length();
217 let mut buf = Vec::with_capacity(len);
218 self.stream.read_to_end(&mut buf)?;
219 self.body = Some(buf.into_boxed_slice());
220 Ok(())
221 }
222
223 pub fn body(&mut self) -> Result<Option<&[u8]>> {
242 if self.body.is_none() {
243 self.read_body_into_buffer()?;
244 }
245 Ok(self.body.as_deref())
246 }
247
248 pub fn has_body(&mut self) -> Result<bool> {
261 Ok(self.body.is_some() || !self.stream.fill_buf()?.is_empty())
262 }
263
264 pub fn read_body(&mut self, out: &mut dyn Write) -> Result<usize> {
269 let mut total = 0;
270 loop {
271 let slice = self.stream.fill_buf()?;
272 if slice.is_empty() {
273 break;
274 }
275 out.write_all(slice)?;
276
277 let len = slice.len();
278 self.stream.consume(len);
279 total += len;
280 }
281 out.flush()?;
282 Ok(total)
283 }
284 pub fn respond(&mut self) -> Result<()> {
289 let response_line = format!(
290 "HTTP/{} {} {}\r\n",
291 self.version,
292 self.status,
293 self.status_msg()
294 );
295 self.stream.get_mut().write_all(response_line.as_bytes())?;
296 let stream = self.stream.get_mut();
297 for (k, v) in &self.response_headers {
298 stream.write_all(k.as_bytes())?;
299 stream.write_all(b": ")?;
300 stream.write_all(v.as_bytes())?;
301 stream.write_all(b"\r\n")?;
302 }
303 stream.write_all(b"\r\n")?;
304 Ok(())
305 }
306 pub fn respond_buf(&mut self, mut buf: &[u8]) -> Result<()> {
311 self.set_header("Content-Length", buf.len().to_string());
312 self.respond_reader(&mut buf)
313 }
314 #[inline]
319 pub fn respond_str(&mut self, text: &str) -> Result<()> {
320 self.respond_buf(text.as_bytes())
321 }
322 pub fn respond_reader(&mut self, reader: &mut dyn Read) -> Result<()> {
327 const CHUNK_SIZE: usize = 1024;
328 let mut buf: [u8; CHUNK_SIZE] = [0; CHUNK_SIZE];
329
330 self.respond()?;
331
332 let stream = self.stream.get_mut();
333 while let Ok(n) = reader.read(&mut buf) {
334 if n == 0 {
335 break;
336 }
337 stream.write_all(&buf[0..n])?;
338 }
339 Ok(())
340 }
341 pub fn respond_chunked(&mut self, reader: &mut dyn Read) -> Result<()> {
348 self.set_header("Transfer-Encoding", "chunked");
349 let mut reader = Chunked::with_default_size(reader);
350 self.respond_reader(&mut reader)
351 }
352 #[inline]
357 pub fn respond_error_page(&mut self) -> Result<()> {
358 self.set_header("Content-Type", "text/html");
359 self.respond_str(&self.error_page())
360 }
361 #[inline]
366 pub fn ok(&mut self) -> Result<()> {
367 self.set_status(200).respond()
368 }
369 #[inline]
374 pub fn forbidden(&mut self) -> Result<()> {
375 self.set_status(403).respond_error_page()
376 }
377 #[inline]
382 pub fn unauthorized(&mut self) -> Result<()> {
383 self.set_status(401).respond_error_page()
384 }
385 #[inline]
390 pub fn not_found(&mut self) -> Result<()> {
391 self.set_status(404).respond_error_page()
392 }
393 #[inline]
398 pub fn server_error(&mut self) -> Result<()> {
399 self.set_status(500).respond_error_page()
400 }
401 #[inline]
402 #[must_use]
403 pub fn is_http_ok(&self) -> bool {
404 self.status.is_http_ok()
405 }
406 #[inline]
407 #[must_use]
408 pub fn is_http_err(&self) -> bool {
409 self.status.is_http_err()
410 }
411 #[inline]
412 #[must_use]
413 pub fn status_msg(&self) -> &'static str {
414 self.status.status_msg()
415 }
416 #[must_use]
418 pub fn error_page(&self) -> String {
419 let code = self.status;
420 let msg = self.status_msg();
421 format!(
422 "<!DOCTYPE html>
423<html lang=\"en\">
424 <head>
425 <meta charset=\"utf-8\">
426 <title>{code} {msg}</title>
427 </head>
428<body>
429 <h1>{code} {msg}</h1>
430</body>
431</html>"
432 )
433 }
434}
435
436impl PartialEq for HttpRequest {
437 fn eq(&self, other: &Self) -> bool {
438 self.method == other.method
439 && self.url == other.url
440 && self.headers == other.headers
441 && self.params == other.params
442 && self.response_headers == other.response_headers
443 && self.version == other.version
444 && self.status == other.status
445 && self.body == other.body
446 }
447}
448
449#[cfg(test)]
450mod test;