aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/handlers.rs149
-rw-r--r--src/http_types.rs87
-rw-r--r--src/lib.rs1
-rw-r--r--src/main.rs170
-rw-r--r--src/request.rs16
-rw-r--r--src/router.rs76
-rw-r--r--src/server.rs2
7 files changed, 263 insertions, 238 deletions
diff --git a/src/handlers.rs b/src/handlers.rs
new file mode 100644
index 0000000..30b10e2
--- /dev/null
+++ b/src/handlers.rs
@@ -0,0 +1,149 @@
+use crate::http_types::*;
+use crate::request::*;
+use crate::response::*;
+use crate::utils::*;
+use std::collections::HashMap;
+
+pub fn handle_echo(request: &Request, ctx: Option<&HashMap<String, String>>) -> Response {
+ let mut headers = HashMap::new();
+ let mut echo_string = "".to_string();
+ let route = request.endpoint.route.clone();
+
+ let mut body = vec![];
+
+ for ch in route.chars().skip(1).skip_while(|&ch| ch != '/').skip(1) {
+ echo_string.push(ch);
+ }
+ if echo_string.chars().last().unwrap() == '/' {
+ echo_string.pop();
+ }
+
+ if let Some(encoding) = request.get_tag("Accept-Encoding") {
+ if encoding.contains("gzip") {
+ headers.insert("Content-Encoding".to_string(), "gzip".to_string());
+ match encode_gzip_string(echo_string.as_str()) {
+ Ok(encoded_bytes) => {
+ println!("In succses");
+ let len = encoded_bytes.len();
+ body = encoded_bytes;
+ headers.insert("Content-Length".to_string(), len.to_string());
+ }
+ Err(err) => {
+ println!("In error {}", &echo_string);
+ println!("Error: {err}");
+ }
+ }
+ } else {
+ let len = echo_string.len();
+ headers.insert("Content-Length".to_string(), len.to_string());
+ body = echo_string.as_bytes().to_owned();
+ }
+ } else {
+ let len = echo_string.len();
+ headers.insert("Content-Length".to_string(), len.to_string());
+ body = echo_string.as_bytes().to_owned();
+ }
+
+ headers.insert("Content-Type".to_string(), "text/plain".to_string());
+
+ Response::new(
+ "1.1".to_string(),
+ StatusCode::Ok,
+ Some(Headers(headers)),
+ Some(body),
+ )
+}
+
+pub fn handle_post_files(request: &Request, ctx: Option<&HashMap<String, String>>) -> Response {
+ // Extract the route regardless of the variant
+ let mut file = "".to_string();
+ let route = request.endpoint.route.clone();
+
+ let mut directory = ctx.unwrap().get(&"dir".to_string()).unwrap().clone();
+ directory.pop(); // remove last slash
+
+ for ch in route.chars().skip(1).skip_while(|&ch| ch != '/') {
+ file.push(ch);
+ }
+ if file.chars().last().unwrap() == '/' {
+ file.pop();
+ }
+ let len = file.len().to_string();
+
+ let full_path = &(directory + &file);
+ let bytes = request.body().as_ref().unwrap();
+ let body = bytes.as_bytes();
+
+ match save_bytes_to_file(body, full_path) {
+ Ok(bytes) => Response::new("1.1".to_string(), StatusCode::Created, None, None),
+ Err(err) => {
+ println!("Error: {err}");
+ Response::new("1.1".to_string(), StatusCode::NotFound, None, None)
+ }
+ }
+}
+
+pub fn handle_files(request: &Request, ctx: Option<&HashMap<String, String>>) -> Response {
+ // Extract the route regardless of the variant
+ let mut file = "".to_string();
+ let route = request.endpoint.route.clone();
+
+ let mut directory = ctx.unwrap().get(&"dir".to_string()).unwrap().clone();
+ directory.pop(); // remove last slash
+
+ for ch in route.chars().skip(1).skip_while(|&ch| ch != '/') {
+ file.push(ch);
+ }
+ if file.chars().last().unwrap() == '/' {
+ file.pop();
+ }
+ let len = file.len().to_string();
+
+ let full_path = &(directory + &file);
+
+ match read_file_as_bytes(full_path) {
+ Ok(bytes) => {
+ let mut headers = HashMap::new();
+ headers.insert(
+ "Content-Type".to_string(),
+ "application/octet-stream".to_string(),
+ );
+ headers.insert("Content-Length".to_string(), bytes.len().to_string());
+ let body = bytes;
+ Response::new(
+ "1.1".to_string(),
+ StatusCode::Ok,
+ Some(Headers(headers)),
+ Some(body),
+ )
+ }
+ Err(_) => Response::new("1.1".to_string(), StatusCode::NotFound, None, None),
+ }
+}
+
+pub fn handle_user_agent(request: &Request, ctx: Option<&HashMap<String, String>>) -> Response {
+ let mut headers = HashMap::new();
+ if let Some(user_agent) = request.get_tag("User-Agent") {
+ let len = user_agent.len().to_string();
+ headers.insert("Content-Type".to_string(), "text/plain".to_string());
+ headers.insert("Content-Length".to_string(), len);
+ let body = user_agent.as_bytes().to_owned();
+ Response::new(
+ "1.1".to_string(),
+ StatusCode::Ok,
+ Some(Headers(headers)),
+ Some(body),
+ )
+ } else {
+ println!("User-Agent isn't present in headers");
+ Response::new("1.1".to_string(), StatusCode::BadRequest, None, None)
+ }
+}
+
+pub fn handle_success(request: &Request, ctx: Option<&HashMap<String, String>>) -> Response {
+ Response::new("1.1".to_string(), StatusCode::Ok, None, None).into()
+}
+
+pub fn handle_not_found(request: Request, ctx: Option<&HashMap<String, String>>) -> Response {
+ Response::new("1.1".to_string(), StatusCode::NotFound, None, None).into()
+}
diff --git a/src/http_types.rs b/src/http_types.rs
index 79bf015..44a9039 100644
--- a/src/http_types.rs
+++ b/src/http_types.rs
@@ -15,21 +15,48 @@ type Route = String;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum Method {
- Get(Route),
- Post(Route),
- Put(Route),
+ GET,
+ POST,
+ PUT,
}
-pub fn get(route: &str) -> Method {
- Method::Get(route.to_string())
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct Endpoint {
+ pub method: Method,
+ pub route: Route,
}
-pub fn post(route: &str) -> Method {
- Method::Post(route.to_string())
+impl Endpoint {
+ fn get(route: &str) -> Self {
+ Self {
+ method: Method::GET,
+ route: route.to_string(),
+ }
+ }
+ fn post(route: &str) -> Self {
+ Self {
+ method: Method::POST,
+ route: route.to_string(),
+ }
+ }
+ fn put(route: &str) -> Self {
+ Self {
+ method: Method::PUT,
+ route: route.to_string(),
+ }
+ }
}
-pub fn put(route: &str) -> Method {
- Method::Put(route.to_string())
+pub fn get(route: &str) -> Endpoint {
+ Endpoint::get(route)
+}
+
+pub fn post(route: &str) -> Endpoint {
+ Endpoint::post(route)
+}
+
+pub fn put(route: &str) -> Endpoint {
+ Endpoint::put(route)
}
impl From<StatusCode> for String {
@@ -44,23 +71,23 @@ impl From<StatusCode> for String {
}
}
-impl From<Method> for String {
- fn from(val: Method) -> Self {
- use Method::*;
- match val {
- Get(route) => "GET ".to_string() + &route,
- Post(route) => "POST ".to_string() + &route,
- Put(route) => "PUT ".to_string() + &route,
+impl From<Endpoint> for String {
+ fn from(val: Endpoint) -> Self {
+ use Method as M;
+ match (val.method, val.route) {
+ (M::GET, route) => "GET ".to_string() + &route,
+ (M::POST, route) => "GET ".to_string() + &route,
+ (M::PUT, route) => "GET ".to_string() + &route,
}
}
}
-impl From<&Method> for String {
- fn from(val: &Method) -> Self {
- use Method::*;
- match val {
- Get(route) => "GET ".to_string() + &route,
- Post(route) => "POST ".to_string() + &route,
- Put(route) => "PUT ".to_string() + &route,
+impl From<&Endpoint> for String {
+ fn from(val: &Endpoint) -> Self {
+ use Method as M;
+ match (&val.method, &val.route) {
+ (M::GET, route) => "GET ".to_string() + &route,
+ (M::POST, route) => "GET ".to_string() + &route,
+ (M::PUT, route) => "GET ".to_string() + &route,
}
}
}
@@ -94,14 +121,13 @@ impl From<&[&str]> for Headers {
}
}
-impl From<String> for Method {
+impl From<String> for Endpoint {
fn from(val: String) -> Self {
- use Method::*;
let request_line = val.split(' ').collect_vec();
let (method, route) = (request_line[0], request_line[1]);
match method {
- "GET" => Get(route.to_string()),
- "POST" => Post(route.to_string()),
+ "GET" => Endpoint::get(route),
+ "POST" => Endpoint::post(route),
_ => {
eprintln!("{method} Not Supported Yet");
unreachable!()
@@ -109,14 +135,13 @@ impl From<String> for Method {
}
}
}
-impl From<&str> for Method {
+impl From<&str> for Endpoint {
fn from(val: &str) -> Self {
- use Method::*;
let request_line = val.split(' ').collect_vec();
let (method, route) = (request_line[0], request_line[1]);
match method {
- "GET" => Get(route.to_string()),
- "POST" => Post(route.to_string()),
+ "GET" => Endpoint::get(route),
+ "POST" => Endpoint::post(route),
_ => {
eprintln!("{method} Not Supported Yet");
unreachable!()
diff --git a/src/lib.rs b/src/lib.rs
index 9d58875..582b67e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,5 @@
pub mod extractor;
+pub mod handlers;
pub mod http_types;
pub mod request;
pub mod response;
diff --git a/src/main.rs b/src/main.rs
index 250f94b..1b08605 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,170 +1,10 @@
-#![allow(unused)]
+use http_server_starter_rust::handlers::*;
+use http_server_starter_rust::http_types::{get, post};
use http_server_starter_rust::router::Router;
-use itertools::Itertools;
-use nom::AsBytes;
-use std::collections::HashMap;
-use std::fs::File;
-use std::io::{self, Read, Write};
-use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, TcpStream};
-use std::str::Utf8Error;
-use std::sync::{Arc, Mutex};
-use std::{str, thread, usize};
-
-use http_server_starter_rust::request::*;
-use http_server_starter_rust::response::*;
use http_server_starter_rust::server::*;
-use http_server_starter_rust::utils::*;
-use http_server_starter_rust::{extractor, http_types::*};
-
-fn handle_echo(request: &Request, ctx: Option<&HashMap<String, String>>) -> Response {
- let mut headers = HashMap::new();
- let mut echo_string = "".to_string();
- let route = match request.method() {
- Method::Get(route) | Method::Post(route) | Method::Put(route) => route,
- };
-
- let mut body = vec![];
-
- for ch in route.chars().skip(1).skip_while(|&ch| ch != '/').skip(1) {
- echo_string.push(ch);
- }
- if echo_string.chars().last().unwrap() == '/' {
- echo_string.pop();
- }
-
- if let Some(encoding) = request.get_tag("Accept-Encoding") {
- if encoding.contains("gzip") {
- headers.insert("Content-Encoding".to_string(), "gzip".to_string());
- match encode_gzip_string(echo_string.as_str()) {
- Ok(encoded_bytes) => {
- println!("In succses");
- let len = encoded_bytes.len();
- body = encoded_bytes;
- headers.insert("Content-Length".to_string(), len.to_string());
- }
- Err(err) => {
- println!("In error {}", &echo_string);
- println!("Error: {err}");
- }
- }
- } else {
- let len = echo_string.len();
- headers.insert("Content-Length".to_string(), len.to_string());
- body = echo_string.as_bytes().to_owned();
- }
- } else {
- let len = echo_string.len();
- headers.insert("Content-Length".to_string(), len.to_string());
- body = echo_string.as_bytes().to_owned();
- }
-
- headers.insert("Content-Type".to_string(), "text/plain".to_string());
-
- Response::new(
- "1.1".to_string(),
- StatusCode::Ok,
- Some(Headers(headers)),
- Some(body),
- )
-}
-
-fn handle_post_files(request: &Request, ctx: Option<&HashMap<String, String>>) -> Response {
- // Extract the route regardless of the variant
- let mut file = "".to_string();
- let route = match request.method() {
- Method::Get(route) | Method::Post(route) | Method::Put(route) => route,
- };
-
- let mut directory = ctx.unwrap().get(&"dir".to_string()).unwrap().clone();
- directory.pop(); // remove last slash
-
- for ch in route.chars().skip(1).skip_while(|&ch| ch != '/') {
- file.push(ch);
- }
- if file.chars().last().unwrap() == '/' {
- file.pop();
- }
- let len = file.len().to_string();
-
- let full_path = &(directory + &file);
- let bytes = request.body().as_ref().unwrap();
- let body = bytes.as_bytes();
-
- match save_bytes_to_file(body, full_path) {
- Ok(bytes) => Response::new("1.1".to_string(), StatusCode::Created, None, None),
- Err(err) => {
- println!("Error: {err}");
- Response::new("1.1".to_string(), StatusCode::NotFound, None, None)
- }
- }
-}
-
-fn handle_files(request: &Request, ctx: Option<&HashMap<String, String>>) -> Response {
- // Extract the route regardless of the variant
- let mut file = "".to_string();
- let route = match request.method() {
- Method::Get(route) | Method::Post(route) | Method::Put(route) => route,
- };
-
- let mut directory = ctx.unwrap().get(&"dir".to_string()).unwrap().clone();
- directory.pop(); // remove last slash
-
- for ch in route.chars().skip(1).skip_while(|&ch| ch != '/') {
- file.push(ch);
- }
- if file.chars().last().unwrap() == '/' {
- file.pop();
- }
- let len = file.len().to_string();
-
- let full_path = &(directory + &file);
-
- match read_file_as_bytes(full_path) {
- Ok(bytes) => {
- let mut headers = HashMap::new();
- headers.insert(
- "Content-Type".to_string(),
- "application/octet-stream".to_string(),
- );
- headers.insert("Content-Length".to_string(), bytes.len().to_string());
- let body = bytes;
- Response::new(
- "1.1".to_string(),
- StatusCode::Ok,
- Some(Headers(headers)),
- Some(body),
- )
- }
- Err(_) => Response::new("1.1".to_string(), StatusCode::NotFound, None, None),
- }
-}
-
-fn handle_user_agent(request: &Request, ctx: Option<&HashMap<String, String>>) -> Response {
- let mut headers = HashMap::new();
- if let Some(user_agent) = request.get_tag("User-Agent") {
- let len = user_agent.len().to_string();
- headers.insert("Content-Type".to_string(), "text/plain".to_string());
- headers.insert("Content-Length".to_string(), len);
- let body = user_agent.as_bytes().to_owned();
- Response::new(
- "1.1".to_string(),
- StatusCode::Ok,
- Some(Headers(headers)),
- Some(body),
- )
- } else {
- println!("User-Agent isn't present in headers");
- Response::new("1.1".to_string(), StatusCode::BadRequest, None, None)
- }
-}
-
-fn handle_success(request: &Request, ctx: Option<&HashMap<String, String>>) -> Response {
- Response::new("1.1".to_string(), StatusCode::Ok, None, None).into()
-}
-
-fn handle_not_found(request: Request, ctx: Option<&HashMap<String, String>>) -> Response {
- Response::new("1.1".to_string(), StatusCode::NotFound, None, None).into()
-}
+use std::collections::HashMap;
+use std::io::{self};
+use std::net::{IpAddr, Ipv4Addr, SocketAddr};
fn main() -> io::Result<()> {
// Collect the command-line arguments
diff --git a/src/request.rs b/src/request.rs
index 7c8f43c..1858d5a 100644
--- a/src/request.rs
+++ b/src/request.rs
@@ -4,13 +4,13 @@ use crate::http_types::*;
#[derive(Debug, Clone)]
pub struct Request {
- pub method: Method,
+ pub endpoint: Endpoint,
pub headers: Option<Headers>,
body: Option<String>,
}
impl Request {
- fn new(method: Method, headers: Headers, body: String) -> Self {
+ fn new(method: Endpoint, headers: Headers, body: String) -> Self {
let headers = if headers.0.len() == 0 {
None
} else {
@@ -18,7 +18,7 @@ impl Request {
};
let body = if body.is_empty() { None } else { Some(body) };
Request {
- method,
+ endpoint: method,
headers,
body,
}
@@ -28,8 +28,8 @@ impl Request {
self.headers.as_ref().unwrap().0.get(&key.to_string())
}
- pub fn method(&self) -> &Method {
- &self.method
+ pub fn endpoint(&self) -> &Endpoint {
+ &self.endpoint
}
pub fn headers(&self) -> &Option<Headers> {
@@ -46,7 +46,7 @@ impl From<Vec<&str>> for Request {
match &value[..] {
[request_line, headers @ .., body] => {
let (method, headers, body) =
- (Method::from(*request_line), Headers::from(headers), body);
+ (Endpoint::from(*request_line), Headers::from(headers), body);
if let Some(content_length) = headers.0.get("Content-Length") {
let content_length = content_length
.parse::<usize>()
@@ -65,7 +65,7 @@ impl From<Vec<&str>> for Request {
impl<'a> Into<String> for Request {
fn into(self) -> String {
- let method = String::from(self.method);
+ let method = String::from(self.endpoint);
let (method, endpoint) = method.split_once(" ").unwrap();
let status_line = format!("{} {} HTTP/1.1", method, endpoint);
let headers = self
@@ -82,7 +82,7 @@ impl<'a> Into<String> for Request {
impl Into<String> for &Request {
fn into(self) -> String {
- let method = String::from(self.method.clone());
+ let method = String::from(self.endpoint.clone());
let (method, endpoint) = method.split_once(" ").unwrap();
let status_line = format!("{} {} HTTP/1.1", method, endpoint);
let headers = self
diff --git a/src/router.rs b/src/router.rs
index 7b1313b..ff9a893 100644
--- a/src/router.rs
+++ b/src/router.rs
@@ -1,6 +1,6 @@
use crate::{
extractor::build_regex_from_path,
- http_types::{Method, StatusCode},
+ http_types::{get, post, put, Endpoint, Method, StatusCode},
request::Request,
response::Response,
};
@@ -8,7 +8,7 @@ use regex::Regex;
use std::collections::HashMap;
type Handler = fn(&Request, Option<&HashMap<String, String>>) -> Response;
-type Routes = HashMap<Method, Handler>;
+type Routes = HashMap<Endpoint, Handler>;
pub struct Router {
routes: Routes,
@@ -27,53 +27,63 @@ impl Router {
}
// Add a route to the router
- pub fn route(&mut self, method: Method, handler: Handler) -> &mut Self {
- use Method::*;
- match method {
- Get(route) => {
+ pub fn route(&mut self, endpoint: Endpoint, handler: Handler) -> &mut Self {
+ use Method as M;
+ match (endpoint.method, endpoint.route) {
+ (M::GET, route) => {
let re = build_regex_from_path(&route);
- let meth = Get(re.to_string());
- self.routes.insert(meth, handler);
+ let epoint = get(re.as_str());
+ self.routes.insert(epoint, handler);
+ }
+ (M::POST, route) => {
+ let re = build_regex_from_path(&route);
+ let epoint = post(re.as_str());
+ self.routes.insert(epoint, handler);
}
- Post(route) => {
+ (M::PUT, route) => {
let re = build_regex_from_path(&route);
- let meth = Post(re.to_string());
+ let meth = put(re.as_str());
self.routes.insert(meth, handler);
}
- Put(_) => todo!(),
}
self
}
// Handle incoming requests
pub fn handle(&self, request: &Request, ctx: Option<&HashMap<String, String>>) -> Response {
- use Method::*;
- match &request.method {
- Get(request_method) => {
- for (method, handler) in self.routes() {
- if let Get(method_string) = method {
- let re = Regex::new(method_string).unwrap();
- // dbg!(&re, request_method);
- if re.is_match(request_method) {
- return handler(request, ctx);
- }
+ use Method as M;
+ for (endpoint, handler) in self.routes() {
+ let repoint = &request.endpoint;
+ match (
+ &repoint.method,
+ &repoint.route,
+ &endpoint.method,
+ &endpoint.route,
+ ) {
+ (M::GET, request_route, M::GET, endpoint_regex) => {
+ let re = Regex::new(&endpoint_regex).unwrap();
+ // dbg!(&re, request_method);
+ if re.is_match(&request_route) {
+ return handler(request, ctx);
}
}
- Response::new("1.1".to_string(), StatusCode::NotFound, None, None).into()
- }
- Post(request_method) => {
- for (method, handler) in self.routes() {
- if let Post(method_string) = method {
- let re = Regex::new(method_string).unwrap();
- // dbg!(&re, request_method);
- if re.is_match(request_method) {
- return handler(request, ctx);
- }
+ (M::POST, request_route, M::POST, endpoint_regex) => {
+ let re = Regex::new(&endpoint_regex).unwrap();
+ // dbg!(&re, request_method);
+ if re.is_match(&request_route) {
+ return handler(request, ctx);
+ }
+ }
+ (M::PUT, request_route, M::PUT, endpoint_regex) => {
+ let re = Regex::new(&endpoint_regex).unwrap();
+ // dbg!(&re, request_method);
+ if re.is_match(&request_route) {
+ return handler(request, ctx);
}
}
- Response::new("1.1".to_string(), StatusCode::NotFound, None, None).into()
+ _ => {}
}
- Put(_) => todo!(),
}
+ Response::new("1.1".to_string(), StatusCode::NotFound, None, None)
}
}
diff --git a/src/server.rs b/src/server.rs
index 9c60d33..6c0eeb1 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -40,7 +40,7 @@ impl Server {
let request_string: String = (&request).into();
println!("Request after parsing:\n{}", request_string);
- dbg!(&request.method);
+ dbg!(&request.endpoint);
let response: Vec<u8> = router.handle(&request, ctx).into();
stream.write(response.as_bytes())