From fe378c20c645ca1cb6cc04e95c9afd4c2de5c0e8 Mon Sep 17 00:00:00 2001 From: omagdy7 Date: Wed, 5 Jun 2024 22:42:05 +0300 Subject: feat: Handled Gzip compression --- Cargo.lock | 27 +++++++++++++++++++++++++++ Cargo.toml | 2 ++ src/main.rs | 41 +++++++++++++++++++++++++++++++---------- src/response.rs | 27 +++++++++++++++++---------- src/server.rs | 4 +++- src/utils.rs | 15 +++++++++++++++ 6 files changed, 95 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 931bb35..096c35d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,6 +53,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -77,6 +83,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "diff" version = "0.1.13" @@ -89,6 +104,16 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "gimli" version = "0.27.3" @@ -106,7 +131,9 @@ name = "http-server-starter-rust" version = "0.1.0" dependencies = [ "anyhow", + "base64", "bytes", + "flate2", "itertools", "nom", "pretty_assertions", diff --git a/Cargo.toml b/Cargo.toml index 6a19fc8..9858303 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,8 @@ tokio = { version = "1.23.0", features = ["full"] } # async networking nom = "7.1.3" # parser combinators itertools = "0.11.0" # General iterator helpers regex = "1.10.4" +flate2 = "1.0.30" +base64 = "0.22.1" [dev-dependencies] pretty_assertions = "1.3.0" # nicer looking assertions diff --git a/src/main.rs b/src/main.rs index 24c75a1..250f94b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,11 +23,7 @@ fn handle_echo(request: &Request, ctx: Option<&HashMap>) -> Resp Method::Get(route) | Method::Post(route) | Method::Put(route) => route, }; - if let Some(encoding) = request.get_tag("Accept-Encoding") { - if encoding.contains("gzip") { - headers.insert("Content-Encoding".to_string(), "gzip".to_string()); - } - } + let mut body = vec![]; for ch in route.chars().skip(1).skip_while(|&ch| ch != '/').skip(1) { echo_string.push(ch); @@ -35,10 +31,35 @@ fn handle_echo(request: &Request, ctx: Option<&HashMap>) -> Resp if echo_string.chars().last().unwrap() == '/' { echo_string.pop(); } - let len = echo_string.len().to_string(); + + 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()); - headers.insert("Content-Length".to_string(), len); - let body = echo_string; + Response::new( "1.1".to_string(), StatusCode::Ok, @@ -106,7 +127,7 @@ fn handle_files(request: &Request, ctx: Option<&HashMap>) -> Res "application/octet-stream".to_string(), ); headers.insert("Content-Length".to_string(), bytes.len().to_string()); - let body = String::from_utf8(bytes).unwrap(); + let body = bytes; Response::new( "1.1".to_string(), StatusCode::Ok, @@ -124,7 +145,7 @@ fn handle_user_agent(request: &Request, ctx: Option<&HashMap>) - 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.to_string(); + let body = user_agent.as_bytes().to_owned(); Response::new( "1.1".to_string(), StatusCode::Ok, diff --git a/src/response.rs b/src/response.rs index c3540d2..80424a3 100644 --- a/src/response.rs +++ b/src/response.rs @@ -5,7 +5,7 @@ pub struct Response { version: String, status: StatusCode, headers: Option, - body: Option, + body: Option>, } impl Response { @@ -13,7 +13,7 @@ impl Response { version: String, status: StatusCode, headers: Option, - body: Option, + body: Option>, ) -> Self { Response { version, @@ -24,18 +24,25 @@ impl Response { } } -impl Into for Response { - fn into(self) -> String { - let status_line = format!("HTTP/{} {}", self.version, String::from(self.status)); +impl Into> for Response { + fn into(self) -> Vec { + let status_line = format!("HTTP/{} {}", self.version, String::from(self.status)) + .as_bytes() + .to_owned(); let headers = self .headers .unwrap_or(Headers(HashMap::new())) .0 .iter() .map(|(key, value)| format!("{key}: {value}\r\n")) - .collect::(); - let body = self.body.unwrap_or("".to_string()); - format!("{status_line}\r\n{headers}\r\n{body}") + .collect::() + .as_bytes() + .to_owned(); + let body = self.body.unwrap_or(vec![]); + let crlf = "\r\n".as_bytes().to_owned(); + vec![status_line, crlf.clone(), headers, crlf.clone(), body] + .concat() + .to_owned() } } @@ -74,12 +81,12 @@ impl Into for &str { }; // Parse body - let body: Option = { + let body: Option> = { let remaining_lines: Vec<&str> = lines.collect(); if remaining_lines.is_empty() { None } else { - Some(remaining_lines.join("\n")) + Some(remaining_lines.join("\n").as_bytes().to_owned()) } }; diff --git a/src/server.rs b/src/server.rs index c0d7ab6..9c60d33 100644 --- a/src/server.rs +++ b/src/server.rs @@ -6,6 +6,8 @@ use std::{ net::{SocketAddr, TcpListener}, }; +use nom::AsBytes; + use crate::request::Request; use crate::router::Router; @@ -40,7 +42,7 @@ impl Server { println!("Request after parsing:\n{}", request_string); dbg!(&request.method); - let response: String = router.handle(&request, ctx).into(); + let response: Vec = router.handle(&request, ctx).into(); stream.write(response.as_bytes()) } Err(_) => todo!(), diff --git a/src/utils.rs b/src/utils.rs index e9d2ef1..925669c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,21 @@ +use flate2::write::GzEncoder; +use flate2::Compression; use std::fs::File; use std::io::{self, Read, Write}; +pub fn encode_gzip_string(input: &str) -> std::io::Result> { + // Create a buffer to hold the compressed data + let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); + + // Write the input string into the encoder + encoder.write_all(input.as_bytes())?; + + // Finish the encoding process and get the compressed data + let compressed_data = encoder.finish()?; + + Ok(compressed_data) +} + pub fn save_bytes_to_file(bytes: &[u8], file_path: &str) -> io::Result<()> { let mut file = File::create(file_path)?; file.write_all(bytes)?; -- cgit v1.2.3