1mod parse;
2use core::fmt;
3use std::{
4 collections::HashMap,
5 env,
6 ffi::OsStr,
7 io,
8 io::{BufRead, BufReader, BufWriter, Read, Write},
9 path::Path,
10};
11
12use parse::parse_request;
13
14use crate::{
15 HttpMethod, HttpResponse, HttpStream, Result, StatusCode,
16 encoding::Chunked,
17 request::builder::{HttpRequestBuilder, NoUrl},
18 stream::IntoHttpStream,
19};
20
21pub mod builder;
22
23pub struct HttpRequest {
27 method: HttpMethod,
28 url: Box<str>,
29 headers: HashMap<Box<str>, Box<str>>,
30 params: HashMap<Box<str>, Box<str>>,
31 response_headers: HashMap<Box<str>, Box<str>>,
32 version: f32,
33 stream: BufReader<Box<dyn HttpStream>>,
34 status: u16,
35 body: Option<Box<[u8]>>,
36}
37
38impl fmt::Debug for HttpRequest {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct("HttpRequest")
41 .field("method", &self.method)
42 .field("url", &self.url)
43 .field("headers", &self.headers)
44 .field("params", &self.params)
45 .field("response_headers", &self.response_headers)
46 .field("version", &self.version)
47 .field("status", &self.status)
48 .field("body", &self.body)
49 .finish()
50 }
51}
52
53impl HttpRequest {
54 pub fn builder() -> HttpRequestBuilder<NoUrl> {
56 HttpRequestBuilder::new()
57 }
58
59 pub fn parse<S: IntoHttpStream>(stream: S) -> Result<Self> {
61 let stream: Box<dyn HttpStream> = Box::new(stream.into_http_stream());
62 parse_request(BufReader::new(stream))
63 }
64
65 #[inline]
66 pub fn keep_alive(self) -> Result<Self> {
67 let mut req = parse_request(self.stream)?;
68 req.set_header("Connection", "keep-alive");
69 Ok(req)
70 }
71
72 #[inline]
73 pub fn stream(&self) -> &BufReader<Box<dyn HttpStream>> {
74 &self.stream
75 }
76
77 #[inline]
78 pub fn stream_mut(&mut self) -> &mut BufReader<Box<dyn HttpStream>> {
79 &mut self.stream
80 }
81
82 #[inline]
84 pub fn url(&self) -> &str {
85 &self.url
86 }
87
88 #[inline]
89 pub fn set_url(&mut self, url: impl Into<Box<str>>) {
90 self.url = url.into();
91 }
92 #[inline]
94 #[must_use]
95 pub fn params(&self) -> &HashMap<Box<str>, Box<str>> {
96 &self.params
97 }
98
99 #[inline]
100 #[must_use]
101 pub fn param(&self, key: &str) -> Option<&str> {
102 self.params.get(key).map(AsRef::as_ref)
103 }
104
105 pub fn filename(&self) -> Result<Box<str>> {
111 let mut cwd = env::current_dir()?;
112 cwd.push(Path::new(OsStr::new(&self.url[1..])));
113 let cwd = cwd.to_str().ok_or("Error getting cwd")?;
114 Ok(Box::from(cwd))
115 }
116
117 pub fn write_to(&self, f: &mut dyn Write) -> Result<()> {
119 write!(f, "{} {}", self.method(), self.url())?;
120 if !self.params().is_empty() {
121 write!(f, "?")?;
122 for (k, v) in self.params() {
123 let ke = url::encode(k).unwrap_or("".into());
124 let ve = url::encode(v).unwrap_or("".into());
125 write!(f, "{ke}={ve}&")?;
126 }
127 }
128 write!(f, " HTTP/{}\r\n", self.version())?;
129
130 for (k, v) in self.headers() {
131 write!(f, "{k}: {v}\r\n")?;
132 }
133
134 if let Some(ref b) = self.body {
135 f.write_all(b)?;
136 }
137
138 write!(f, "\r\n")?;
139
140 Ok(())
141 }
142
143 pub fn send_to<Out: IntoHttpStream>(&self, stream: Out) -> crate::Result<HttpResponse> {
148 let mut stream = stream.into_http_stream();
149 self.write_to(&mut stream)?;
150 stream.flush()?;
151 HttpResponse::parse(stream)
152 }
153 #[inline]
154 #[must_use]
155 pub fn method(&self) -> &HttpMethod {
156 &self.method
157 }
158
159 #[inline]
160 #[must_use]
161 pub fn status(&self) -> u16 {
162 self.status
163 }
164
165 #[inline]
166 pub fn set_status(&mut self, status: u16) -> &mut Self {
167 self.status = status;
168 self
169 }
170
171 #[inline]
172 #[must_use]
173 pub fn version(&self) -> f32 {
174 self.version
175 }
176
177 #[inline]
182 #[must_use]
183 pub fn content_length(&self) -> usize {
184 match self.headers.get("Content-Length") {
185 Some(l) => l.parse().unwrap_or(0),
186 None => 0,
187 }
188 }
189
190 #[inline]
192 #[must_use]
193 pub fn header(&self, key: &str) -> Option<&str> {
194 self.headers.get(key).map(AsRef::as_ref)
195 }
196
197 #[inline]
198 #[must_use]
199 pub fn headers(&self) -> &HashMap<Box<str>, Box<str>> {
200 &self.headers
201 }
202
203 #[inline]
204 pub fn set_header(&mut self, key: impl Into<Box<str>>, value: impl Into<Box<str>>) {
205 self.response_headers.insert(key.into(), value.into());
206 }
207
208 pub(crate) fn read_body_into_buffer(&mut self) -> Result<()> {
214 let len = self.content_length();
215 let mut buf = Vec::with_capacity(len);
216 self.stream.read_to_end(&mut buf)?;
217 self.body = Some(buf.into_boxed_slice());
218 Ok(())
219 }
220
221 pub fn body(&mut self) -> Result<Option<&[u8]>> {
240 if self.body.is_none() {
241 self.read_body_into_buffer()?;
242 }
243 Ok(self.body.as_deref())
244 }
245
246 pub fn has_body(&mut self) -> Result<bool> {
259 Ok(self.body.is_some() || !self.stream.fill_buf()?.is_empty())
260 }
261
262 pub fn read_body(&mut self, out: &mut dyn Write) -> Result<usize> {
267 let mut total = 0;
268 loop {
269 let slice = self.stream.fill_buf()?;
270 if slice.is_empty() {
271 break;
272 }
273 out.write_all(slice)?;
274
275 let len = slice.len();
276 self.stream.consume(len);
277 total += len;
278 }
279 out.flush()?;
280 Ok(total)
281 }
282 pub fn respond(&mut self) -> Result<()> {
287 let response_line = format!(
288 "HTTP/{} {} {}\r\n",
289 self.version,
290 self.status,
291 self.status_msg()
292 );
293 self.stream.get_mut().write_all(response_line.as_bytes())?;
294 let stream = self.stream.get_mut();
295 for (k, v) in &self.response_headers {
296 stream.write_all(k.as_bytes())?;
297 stream.write_all(b": ")?;
298 stream.write_all(v.as_bytes())?;
299 stream.write_all(b"\r\n")?;
300 }
301 stream.write_all(b"\r\n")?;
302 Ok(())
303 }
304 pub fn respond_buf(&mut self, mut buf: &[u8]) -> Result<()> {
309 self.set_header("Content-Length", buf.len().to_string());
310 self.respond_reader(&mut buf)
311 }
312 #[inline]
317 pub fn respond_str(&mut self, text: &str) -> Result<()> {
318 self.respond_buf(text.as_bytes())
319 }
320 pub fn respond_with<F>(&mut self, f: F) -> Result<()>
325 where
326 F: FnOnce(&mut dyn Write) -> Result<()>,
327 {
328 self.respond()?;
329 let mut out = BufWriter::new(self.stream.get_mut());
330 f(&mut out)?;
331 out.flush()?;
332 Ok(())
333 }
334 pub fn respond_reader(&mut self, reader: &mut dyn Read) -> Result<()> {
339 self.respond()?;
340 let stream = self.stream.get_mut();
341 io::copy(reader, stream)?;
342 Ok(())
343 }
344 pub fn respond_chunked(&mut self, reader: &mut dyn Read) -> Result<()> {
351 self.set_header("Transfer-Encoding", "chunked");
352 let mut reader = Chunked::with_default_size(reader);
353 self.respond_reader(&mut reader)
354 }
355 #[inline]
360 pub fn respond_error_page(&mut self) -> Result<()> {
361 self.set_header("Content-Type", "text/html");
362 self.respond_str(&self.error_page())
363 }
364 #[inline]
369 pub fn ok(&mut self) -> Result<()> {
370 self.set_status(200).respond()
371 }
372 #[inline]
377 pub fn forbidden(&mut self) -> Result<()> {
378 self.set_status(403).respond_error_page()
379 }
380 #[inline]
385 pub fn unauthorized(&mut self) -> Result<()> {
386 self.set_status(401).respond_error_page()
387 }
388 #[inline]
393 pub fn not_found(&mut self) -> Result<()> {
394 self.set_status(404).respond_error_page()
395 }
396 #[inline]
401 pub fn server_error(&mut self) -> Result<()> {
402 self.set_status(500).respond_error_page()
403 }
404 #[inline]
405 #[must_use]
406 pub fn is_http_ok(&self) -> bool {
407 self.status.is_http_ok()
408 }
409 #[inline]
410 #[must_use]
411 pub fn is_http_err(&self) -> bool {
412 self.status.is_http_err()
413 }
414 #[inline]
415 #[must_use]
416 pub fn status_msg(&self) -> &'static str {
417 self.status.status_msg()
418 }
419 #[must_use]
421 pub fn error_page(&self) -> String {
422 let code = self.status;
423 let msg = self.status_msg();
424 format!(
425 "<!DOCTYPE html>
426<html lang=\"en\">
427 <head>
428 <meta charset=\"utf-8\">
429 <title>{code} {msg}</title>
430 </head>
431<body>
432 <h1>{code} {msg}</h1>
433</body>
434</html>"
435 )
436 }
437}
438
439impl PartialEq for HttpRequest {
440 fn eq(&self, other: &Self) -> bool {
441 self.method == other.method
442 && self.url == other.url
443 && self.headers == other.headers
444 && self.params == other.params
445 && self.response_headers == other.response_headers
446 && self.version == other.version
447 && self.status == other.status
448 && self.body == other.body
449 }
450}
451
452#[cfg(test)]
453mod test;