diff options
author | Tyler Murphy <tylerm@tylerm.dev> | 2023-07-02 18:45:41 -0400 |
---|---|---|
committer | Tyler Murphy <tylerm@tylerm.dev> | 2023-07-02 18:45:41 -0400 |
commit | cbb92993b592e6b68dbce7f283fb73d19fd1793e (patch) | |
tree | 975d41a0cd077cee9c0d9162b82256b4e8beb07b /src/http | |
download | bashttp-cbb92993b592e6b68dbce7f283fb73d19fd1793e.tar.gz bashttp-cbb92993b592e6b68dbce7f283fb73d19fd1793e.tar.bz2 bashttp-cbb92993b592e6b68dbce7f283fb73d19fd1793e.zip |
hi
Diffstat (limited to 'src/http')
-rw-r--r-- | src/http/code.rs | 8 | ||||
-rw-r--r-- | src/http/header.rs | 96 | ||||
-rw-r--r-- | src/http/method.rs | 30 | ||||
-rw-r--r-- | src/http/mod.rs | 6 | ||||
-rw-r--r-- | src/http/request.rs | 52 | ||||
-rw-r--r-- | src/http/response.rs | 42 | ||||
-rw-r--r-- | src/http/uri.rs | 106 |
7 files changed, 340 insertions, 0 deletions
diff --git a/src/http/code.rs b/src/http/code.rs new file mode 100644 index 0000000..ba47282 --- /dev/null +++ b/src/http/code.rs @@ -0,0 +1,8 @@ +#[derive(Debug, Clone)] +#[allow(dead_code)] +pub enum Code { + Success = 200, + MethodNotAllowed = 405, + TooManyRequests = 429, + InternalServerError = 500, +} diff --git a/src/http/header.rs b/src/http/header.rs new file mode 100644 index 0000000..e6fc552 --- /dev/null +++ b/src/http/header.rs @@ -0,0 +1,96 @@ +use std::{collections::HashMap, str::Split}; + +#[derive(Debug, Clone)] +pub struct HeaderMap { + map: HashMap<String, usize>, + headers: Vec<Header> +} + +impl HeaderMap { + + #[allow(dead_code)] + pub fn get(&self, key: &str) -> Option<&Header> { + let Some(index) = self.map.get(key) else { + return None; + }; + return Some(&self.headers[index.to_owned()]); + } + + pub fn put(&mut self, header: Header) { + if let Some(index) = self.map.get(&header.key) { + self.headers[index.to_owned()] = header; + return + } + + let index = self.headers.len(); + self.map.insert(header.key.clone(), index); + self.headers.push(header); + } + + #[allow(dead_code)] + pub fn del(&mut self, key: &str) -> Option<Header> { + let Some(index) = self.map.get(key) else { + return None + }; + + let removed = self.headers.remove(index.to_owned()); + for i in (index.to_owned())..self.headers.len() { + let key = &self.headers[i].key; + + let Some(index) = self.map.get(key) else { + continue; + }; + + self.map.insert(key.clone(), index - 1); + } + + Some(removed) + } + + pub fn deserialize(&self) -> String { + let mut string = String::new(); + + for header in &self.headers { + string += &format!("{}: {}\n", header.key, header.value); + } + + string + } + + pub fn serialize(lines: &mut Split<char>) -> Self { + + let mut headers = Self::new(); + + loop { + let Some(header) = lines.next() else { break }; + if header.trim().len() < 1 { break } + + let mut parts = header.split(": ").into_iter(); + let Some(key) = parts.next() else { continue }; + let Some(value) = parts.next() else { continue }; + + headers.put(Header::new(key.trim(), value.trim())); + } + + headers + } + + pub fn new() -> Self { + Self { + map: HashMap::new(), + headers: Vec::new() + } + } +} + +#[derive(Debug, Clone)] +pub struct Header { + pub key: String, + pub value: String +} + +impl Header { + pub fn new(key: &str, value: &str) -> Self { + return Self {key: key.to_owned(), value: value.to_owned()} + } +} diff --git a/src/http/method.rs b/src/http/method.rs new file mode 100644 index 0000000..55cea65 --- /dev/null +++ b/src/http/method.rs @@ -0,0 +1,30 @@ +#[derive(Debug, Clone)] +pub enum Method { + Get, + Head, + Post, + Put, + Delete, + Connect, + Options, + Trace, + Patch +} + +impl Method { + pub fn serialize(string: &str) -> Option<Self> { + match string { + "GET" => Some(Self::Get), + "HEAD" => Some(Self::Head), + "POST" => Some(Self::Post), + "PUT" => Some(Self::Put), + "DELETE" => Some(Self::Delete), + "CONNECT" => Some(Self::Connect), + "OPTIONS" => Some(Self::Options), + "TRACE" => Some(Self::Trace), + "PATCH" => Some(Self::Patch), + _ => None + } + } +} + diff --git a/src/http/mod.rs b/src/http/mod.rs new file mode 100644 index 0000000..62151bb --- /dev/null +++ b/src/http/mod.rs @@ -0,0 +1,6 @@ +pub mod code; +pub mod method; +pub mod uri; +pub mod request; +pub mod response; +pub mod header; diff --git a/src/http/request.rs b/src/http/request.rs new file mode 100644 index 0000000..5ba72c9 --- /dev/null +++ b/src/http/request.rs @@ -0,0 +1,52 @@ +use super::{method::Method, uri::URI, header::HeaderMap}; + +#[derive(Debug, Clone)] +pub struct Request { + pub method: Method, + pub uri: URI, + pub headers: HeaderMap, + pub body: Option<String> +} + +impl Request { + pub fn serialize(req: &str) -> Option<Self> { + let mut lines = req.split('\n').to_owned(); + + let Some(head) = lines.next() else { + eprintln!("missing head str"); + return None + }; + + let mut parts = head.trim().split(" "); + + let Some(method_str) = parts.next() else { + eprintln!("missing method str"); + return None + }; + + let Some(method) = Method::serialize(method_str.trim()) else { + eprintln!("invalid http method"); + return None + }; + + let Some(uri_str) = parts.next() else { + eprintln!("missing uri str"); + return None + }; + + let Some(uri) = URI::serialize(uri_str.trim()) else { + eprintln!("invalid http uri"); + return None + }; + + let headers = HeaderMap::serialize(&mut lines); + let body: String = lines.collect(); + + Some(Self { + method, + uri, + headers, + body: if body.len() > 0 { Some(body) } else { None }, + }) + } +} diff --git a/src/http/response.rs b/src/http/response.rs new file mode 100644 index 0000000..850f41e --- /dev/null +++ b/src/http/response.rs @@ -0,0 +1,42 @@ +use super::{code::Code, header::{HeaderMap, Header}}; + +#[derive(Debug, Clone)] +pub struct Response { + pub status: Code, + pub headers: HeaderMap, + pub body: Option<String> +} + +impl Response { + + pub fn new() -> Self { + + let mut headers = HeaderMap::new(); + headers.put(Header::new("Connection", "close")); + + let date = chrono::offset::Utc::now(); + headers.put(Header::new("Date", &date.to_rfc2822())); + + headers.put(Header::new("Server", "bashttp")); + + return Self { + status: Code::Success, + headers, + body: None + } + } + + pub fn deserialize(&self) -> String { + let mut string = String::new(); + + string += &format!("HTTP/1.1 {}\n", self.status.clone() as u16); + string += &self.headers.deserialize(); + + if let Some(body) = &self.body { + string += "\n"; + string += body; + } + + string + } +} diff --git a/src/http/uri.rs b/src/http/uri.rs new file mode 100644 index 0000000..8faff69 --- /dev/null +++ b/src/http/uri.rs @@ -0,0 +1,106 @@ +#[derive(Debug, Clone)] +pub enum Protocol { + HTTP, + HTTPS, +} + +impl Protocol { + pub fn serialize(string: &str) -> Option<Self> { + match string { + "http" => return Some(Self::HTTP), + "https" => return Some(Self::HTTPS), + _ => return None + } + } + + pub fn deserialize(&self) -> &str { + match self { + Self::HTTP => "http", + Self::HTTPS => "https", + } + } +} + +#[derive(Debug, Clone)] +pub struct URI { + pub protocol: Option<Protocol>, + pub host: Option<String>, + pub port: Option<u16>, + pub route: String +} + +impl URI { + + #[allow(dead_code)] + pub fn deserialize(&self) -> String { + let mut string = String::new(); + + if let Some(protocol) = &self.protocol { + string += protocol.deserialize(); + string += "://"; + } + if let Some(host) = &self.host { + string += host; + } + if let Some(port) = self.port { + string += &format!(":{port}"); + } + string += &self.route; + + string + } + + pub fn serialize(head: &str) -> Option<Self> { + + let protocol_end = match head.find("://") { + Some(i) => i, + None => 0 + }; + + let protocol: Option<Protocol>; + let host_start: usize; + if protocol_end == 0 { + protocol = None; + host_start = 0; + } else { + let Some(p) = Protocol::serialize(&head[..protocol_end]) else { + return None + }; + protocol = Some(p); + host_start = protocol_end + 3; + } + + let host_route = &head[host_start..]; + let host_end = host_route.find("/").unwrap_or(head.len()); + + let host: Option<String>; + let port: Option<u16>; + if host_start == host_end { + host = None; + port = None; + } else { + if let Some (host_split) = host_route.find(":") { + let port_start = host_split + 1; + let port_str = &head[port_start..host_end]; + let Ok(p) = port_str.parse::<u16>() else { + return None + }; + host = Some(head[host_start..host_split].to_owned()); + port = Some(p); + } else { + host = Some(head[host_start..host_end].to_owned()); + port = None; + } + } + + let route = &head[host_end..]; + + Some(Self { + protocol, + host, + port, + route: route.to_owned() + }) + } + +} |