snowboard/
request.rs

1use std::net::SocketAddr;
2use std::{borrow::Cow, collections::HashMap};
3
4use crate::{Method, Url};
5
6#[cfg(feature = "json")]
7use crate::ResponseLike;
8
9/// A server request.
10/// Parses the raw request string into a more usable format.
11#[derive(Debug, Clone, PartialEq, Eq)]
12#[cfg_attr(feature = "json", derive(serde::Serialize))]
13pub struct Request {
14	/// The ip from the socket connection.
15	pub ip: SocketAddr,
16	/// Raw URL string.
17	/// Use `Request::parse_url()` to get a parsed version of the URL
18	pub url: String,
19	/// Method used in the request. Might be Method::Unknown if parsing fails.
20	pub method: Method,
21	/// Body of the request, in bytes.
22	/// Use [`Request::text`], [`Request::json`], or [`Request::force_json`]
23	/// to get a parsed version of the body.
24	pub body: Vec<u8>,
25	/// Parsed headers.
26	pub headers: HashMap<String, String>,
27}
28
29impl Request {
30	/// Parses and creates a requeset from raw text and an ip address.
31	/// Note that this does not parse the url (See [Request::url]).
32	pub fn new(bytes: &[u8], ip: SocketAddr) -> Option<Self> {
33		let mut lines = bytes.split(|&byte| byte == b'\n');
34
35		let first_line = String::from_utf8(lines.next()?.to_vec()).ok()?;
36		let mut parts = first_line.split_whitespace();
37
38		let method = Method::from(parts.next()?);
39		let url = parts.next()?.into();
40
41		// Default capacity for headers is 12, but it will grow automatically if needed.
42		let mut headers = HashMap::with_capacity(12);
43
44		let mut in_body = false;
45		let mut body = Vec::new();
46
47		for line in lines {
48			match (in_body, line == b"\r") {
49				(false, true) => in_body = true,
50				(true, _) => {
51					body.extend_from_slice(line);
52					body.push(0x0a /* newline byte */)
53				}
54				_ => {
55					if let Some((key, value)) = Self::parse_header(line) {
56						headers.insert(key, value);
57					}
58				}
59			}
60		}
61
62		body.pop(); // Remove last newline byte (0x0a)
63
64		Some(Self {
65			ip,
66			url,
67			method,
68			body,
69			headers,
70		})
71	}
72
73	fn parse_header(line: &[u8]) -> Option<(String, String)> {
74		let pos = line.iter().position(|&byte| byte == b':')?;
75		let (key, rest) = line.split_at(pos);
76		let value = &rest[1..];
77
78		Some((
79			String::from_utf8_lossy(key).trim().to_string(),
80			String::from_utf8_lossy(value).trim().to_string(),
81		))
82	}
83
84	/// Safely gets a header.
85	pub fn get_header(&self, key: &str) -> Option<&str> {
86		self.headers.get(key).map(|s| s.as_str())
87	}
88
89	/// Equivalent to `get_header(key).unwrap_or(default)`
90	pub fn get_header_or(&self, key: &str, default: &'static str) -> &str {
91		self.get_header(key).unwrap_or(default)
92	}
93
94	/// Checks if a header exists.
95	pub fn has_header(&self, key: &str) -> bool {
96		self.headers.contains_key(key)
97	}
98
99	/// Sets a header using any key and value convertible to Strings
100	pub fn set_header<T: ToString, K: ToString>(&mut self, k: T, v: K) {
101		self.headers.insert(k.to_string(), v.to_string());
102	}
103
104	/// Gets the length of the body.
105	pub fn len(&self) -> usize {
106		self.body.len()
107	}
108
109	/// Checks if the body is empty.
110	pub fn is_empty(&self) -> bool {
111		self.body.is_empty()
112	}
113
114	/// Gets the body as a string.
115	/// See [`String::from_utf8_lossy`]
116	pub fn text(&self) -> Cow<'_, str> {
117		String::from_utf8_lossy(&self.body)
118	}
119
120	/// Get the body as a JSON value.
121	///
122	/// This is only intended for custom invalid JSON handling.
123	/// Use [`Request::force_json`] to be able to use the `?` operator.
124	#[cfg(feature = "json")]
125	pub fn json<T>(&self) -> serde_json::Result<T>
126	where
127		T: for<'a> serde::de::Deserialize<'a>,
128	{
129		serde_json::from_slice(&self.body)
130	}
131
132	/// Get the body as a JSON value, converting a parse error to a bad request response.
133	///
134	/// # Example
135	/// ```rust
136	/// # extern crate serde;
137	/// # extern crate serde_json;
138	/// use snowboard::Server;
139	/// use serde::Deserialize;
140	///
141	/// #[derive(Deserialize)]
142	/// struct MyBody {
143	/// 	foo: String,
144	/// }
145	///
146	/// fn main() -> snowboard::Result {
147	/// 	Server::new("localhost:3000")?.run(|r| {
148	/// 		let body: MyBody = r.force_json()?;
149	///
150	/// 		Ok(serde_json::json!({
151	/// 			"foo": body.foo,
152	/// 		}))
153	/// 	})
154	/// }
155	/// ```
156	#[cfg(feature = "json")]
157	pub fn force_json<T>(&self) -> Result<T, crate::Response>
158	where
159		T: for<'a> serde::de::Deserialize<'a>,
160	{
161		self.json().map_err(|e| e.to_response())
162	}
163
164	/// Get a parsed version of the URL.
165	/// See [Url]
166	pub fn parse_url(&self) -> Url {
167		self.url.as_str().into()
168	}
169
170	/// Get the IP address of the client, formatted.
171	pub fn pretty_ip(&self) -> String {
172		crate::util::format_addr(self.ip)
173	}
174}