Skip to content

Instantly share code, notes, and snippets.

@42LM
Created September 17, 2025 08:57
Show Gist options
  • Select an option

  • Save 42LM/f7d265951abf852a535979c5b51b9818 to your computer and use it in GitHub Desktop.

Select an option

Save 42LM/f7d265951abf852a535979c5b51b9818 to your computer and use it in GitHub Desktop.
Rust client
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