Created
September 17, 2025 08:57
-
-
Save 42LM/f7d265951abf852a535979c5b51b9818 to your computer and use it in GitHub Desktop.
Rust client
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::borrow::Cow; | |
| use std::collections::HashMap; | |
| use std::io::{BufRead, BufReader, Read, Write}; | |
| use std::net::TcpStream; | |
| use std::str; | |
| #[derive(Debug)] | |
| pub enum RequestMethod { | |
| Get, | |
| Post(Option<Vec<u8>>), | |
| Put(Option<Vec<u8>>), | |
| Delete, | |
| } | |
| #[derive(Debug)] | |
| pub struct Request<'a> { | |
| method: RequestMethod, | |
| path: &'a str, | |
| headers: HashMap<Cow<'a, str>, Cow<'a, str>>, | |
| } | |
| pub struct RequestBuilder<'a> { | |
| method: RequestMethod, | |
| path: &'a str, | |
| headers: HashMap<Cow<'a, str>, Cow<'a, str>>, | |
| } | |
| impl<'a> Request<'a> { | |
| pub fn builder() -> RequestBuilder<'a> { | |
| RequestBuilder::default() | |
| } | |
| fn serialize(&self, hostname: &str) -> String { | |
| let mut request = String::new(); | |
| match self.method { | |
| RequestMethod::Get => request.push_str(&format!("GET {} HTTP/1.1\r\n", self.path)), | |
| RequestMethod::Post(_) => request.push_str(&format!("POST {} HTTP/1.1\r\n", self.path)), | |
| RequestMethod::Put(_) => request.push_str(&format!("PUT {} HTTP/1.1\r\n", self.path)), | |
| RequestMethod::Delete => { | |
| request.push_str(&format!("DELETE {} HTTP/1.1\r\n", self.path)) | |
| } | |
| } | |
| request.push_str(&format!("Host: {}\r\n", hostname)); | |
| for (key, value) in &self.headers { | |
| request.push_str(&format!("{}: {}\r\n", key, value)); | |
| } | |
| request.push_str("\r\n"); | |
| if let RequestMethod::Post(Some(body)) | RequestMethod::Put(Some(body)) = &self.method { | |
| request.push_str(str::from_utf8(body).unwrap_or("")); // Assuming UTF-8 for simplicity | |
| } | |
| request | |
| } | |
| } | |
| impl<'a> Default for RequestBuilder<'a> { | |
| fn default() -> Self { | |
| RequestBuilder { | |
| method: RequestMethod::Get, // Providing a default value here | |
| path: <&'a str>::default(), | |
| headers: HashMap::default(), | |
| } | |
| } | |
| } | |
| impl<'a> RequestBuilder<'a> { | |
| pub fn method(mut self, method: RequestMethod) -> Self { | |
| self.method = method; | |
| self | |
| } | |
| pub fn get(mut self, path: &'a str) -> Self { | |
| self.method = RequestMethod::Get; | |
| self.path = path; | |
| self | |
| } | |
| pub fn post(mut self, path: &'a str, body: Option<Vec<u8>>) -> Self { | |
| self.method = RequestMethod::Post(body); | |
| self.path = path; | |
| self | |
| } | |
| pub fn put(mut self, path: &'a str, body: Option<Vec<u8>>) -> Self { | |
| self.method = RequestMethod::Put(body); | |
| self.path = path; | |
| self | |
| } | |
| pub fn delete(mut self, path: &'a str) -> Self { | |
| self.method = RequestMethod::Delete; | |
| self.path = path; | |
| self | |
| } | |
| pub fn header<K, V>(mut self, key: K, value: V) -> Self | |
| where | |
| K: Into<Cow<'a, str>>, | |
| V: Into<Cow<'a, str>>, | |
| { | |
| self.headers.insert(key.into(), value.into()); | |
| self | |
| } | |
| pub fn body(mut self, body: Vec<u8>) -> Self { | |
| match &mut self.method { | |
| RequestMethod::Post(b) | RequestMethod::Put(b) => *b = Some(body), | |
| _ => {} | |
| } | |
| self | |
| } | |
| pub fn build(self) -> Request<'a> { | |
| Request { | |
| method: self.method, | |
| path: self.path, | |
| headers: self.headers, | |
| } | |
| } | |
| } | |
| #[derive(Debug)] | |
| pub struct Response { | |
| pub status_code: u16, | |
| pub headers: HashMap<String, String>, | |
| pub body: Vec<u8>, | |
| } | |
| pub fn send_request( | |
| hostname: &str, | |
| port: u16, | |
| request: &Request, | |
| ) -> Result<Response, std::io::Error> { | |
| let address = format!("{}:{}", hostname, port); | |
| let mut stream = TcpStream::connect(address)?; | |
| let serialized_request = request.serialize(hostname); | |
| stream.write_all(serialized_request.as_bytes())?; | |
| let mut reader = BufReader::new(&mut stream); | |
| let mut status_line = String::new(); | |
| reader.read_line(&mut status_line)?; | |
| let parts: Vec<&str> = status_line.trim().split_whitespace().collect(); | |
| if parts.len() < 2 || parts[0] != "HTTP/1.1" { | |
| return Err(std::io::Error::new( | |
| std::io::ErrorKind::InvalidData, | |
| "Invalid HTTP status line", | |
| )); | |
| } | |
| let status_code = parts[1] | |
| .parse::<u16>() | |
| .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid status code"))?; | |
| let mut headers = HashMap::new(); | |
| loop { | |
| let mut header_line = String::new(); | |
| reader.read_line(&mut header_line)?; | |
| let trimmed_line = header_line.trim(); | |
| if trimmed_line.is_empty() { | |
| break; | |
| } | |
| if let Some(colon_index) = trimmed_line.find(':') { | |
| let key = trimmed_line[..colon_index].trim().to_string(); | |
| let value = trimmed_line[colon_index + 1..].trim().to_string(); | |
| headers.insert(key, value); | |
| } | |
| } | |
| let mut body = Vec::new(); | |
| reader.read_to_end(&mut body)?; | |
| Ok(Response { | |
| status_code, | |
| headers, | |
| body, | |
| }) | |
| } | |
| fn main() -> Result<(), std::io::Error> { | |
| let request = Request::builder() | |
| .get("/todos/1") | |
| .header("User-Agent", "MyRustClient/1.0") | |
| .build(); | |
| match send_request("jsonplaceholder.typicode.com", 80, &request) { | |
| Ok(response) => { | |
| println!("Status Code: {}", response.status_code); | |
| println!("Headers:"); | |
| for (key, value) in &response.headers { | |
| println!(" {}: {}", key, value); | |
| } | |
| println!("Body: {}", String::from_utf8_lossy(&response.body)); | |
| } | |
| Err(e) => { | |
| eprintln!("Error: {}", e); | |
| } | |
| } | |
| let post_data = r#"{"title": "foo", "body": "bar", "userId": 1}"#.as_bytes().to_vec(); | |
| let post_request = Request::builder() | |
| .post("/posts", Some(post_data)) | |
| .header("Content-Type", "application/json") | |
| .header("User-Agent", "MyRustClient/1.0") | |
| .build(); | |
| match send_request("jsonplaceholder.typicode.com", 80, &post_request) { | |
| Ok(response) => { | |
| println!("\nStatus Code (POST): {}", response.status_code); | |
| println!("Headers (POST):"); | |
| for (key, value) in &response.headers { | |
| println!(" {}: {}", key, value); | |
| } | |
| println!("Body (POST): {}", String::from_utf8_lossy(&response.body)); | |
| } | |
| Err(e) => { | |
| eprintln!("Error (POST): {}", e); | |
| } | |
| } | |
| Ok(()) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment