Browse Source

Added bodsy parsing and JSON parsing

master
astria 3 months ago
parent
commit
75f08d2612
  1. 2
      Cargo.lock
  2. 2
      Cargo.toml
  3. 18
      requests/auth.http
  4. 68
      src/http.rs
  5. 4
      src/main.rs
  6. 6
      src/models/HttpRequest.rs
  7. 9
      src/models/http_header.rs
  8. 3
      src/models/mod.rs
  9. 9
      src/models/user.rs
  10. 64
      src/parsers.rs
  11. 46
      src/routes/auth.rs
  12. 12
      src/routes/proxy.rs
  13. 2
      src/routes/statics.rs
  14. 7
      src/utils/db.rs

2
Cargo.lock

@ -105,6 +105,8 @@ checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
name = "chatty" name = "chatty"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"serde",
"serde_json",
"sqlx", "sqlx",
"tokio", "tokio",
] ]

2
Cargo.toml

@ -4,5 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
serde = "1.0.225"
serde_json = { version = "1.0.145", features = ["default"] }
sqlx = {version = "0.8.6", features = ["postgres", "runtime-tokio"]} sqlx = {version = "0.8.6", features = ["postgres", "runtime-tokio"]}
tokio = {version = "1.47.1", features = ["full"]} tokio = {version = "1.47.1", features = ["full"]}

18
requests/auth.http

@ -0,0 +1,18 @@
###
POST /api/auth/login/ HTTP/1.1
Host: localhost:7878
User-Agent: curl/7.68.0
Accept: */*
###
POST /api/auth/register HTTP/1.1
Host: localhost:7878
User-Agent: curl/7.68.0
Accept: */*
Content-Type: application/json
{
"username": "astria",
"email": "astria@example.com",
"password": "securepassword"
}

68
src/http.rs

@ -1,9 +1,9 @@
use std::net::{TcpListener, TcpStream}; use std::net::{TcpListener, TcpStream};
use std::thread; use std::thread;
use std::io::{BufReader, prelude::*}; use std::io::{BufReader, BufRead, Write, prelude::*};
use std::boxed::Box; use crate::models::{http_header::HttpHeader, HttpRequest::HttpRequest, user::User};
use crate::routes::proxy; use crate::parsers::{parse_headers, parse_json_body};
use crate::models::HttpRequest::HttpRequest; use crate::routes::proxy::proxy;
pub fn http_server() { pub fn http_server() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
@ -24,43 +24,47 @@ pub fn http_server() {
} }
fn handle_connection(mut stream: TcpStream) { fn handle_connection(mut stream: TcpStream) {
// Handle the connection (currently does nothing) let mut reader = BufReader::new(&mut stream);
let reader = BufReader::new(&mut stream); let mut header_lines: Vec<String> = Vec::new();
let http_request = reader.lines().next().unwrap().unwrap();
let parsed_request = parse_http_request(&http_request); // Read the HTTP request line by line
match parsed_request { loop {
Ok(request) => { let mut line = String::new();
proxy::proxy(request, &stream); match reader.read_line(&mut line) {
Ok(0) => break, // EOF
Ok(_) => {
println!("Received line: {}", line.trim());
if line.trim().is_empty() {
break; // End of headers
}
header_lines.push(line);
} }
Err(e) => { Err(e) => {
eprintln!("Failed to parse HTTP request: {}", e); eprintln!("Error reading request: {}", e);
break;
} }
} }
}
println!("Finished!");
let headers = parse_headers(header_lines);
let mut body = String::new();
} if let Some(length) = headers.content_length {
let mut body_buffer = vec![0; length];
// FUNCTION TO PARSE HTTP REQUESTS if let Err(e) = reader.read_exact(&mut body_buffer) {
fn parse_http_request(request: &str) -> Result<HttpRequest, Box<dyn std::error::Error>> { eprintln!("Error reading body: {}", e);
let parts: Vec<&str> = request.split_whitespace().collect(); } else {
body = String::from_utf8_lossy(&body_buffer).to_string();
if parts[1].ends_with('/') == false && parts[1].contains('.') == false { }
// ADD TRAILING / IF MISSING
println!("Adding trailing / to path");
let new_path = format!("{}/", parts[1]);
let new_request = format!("{} {} {}", parts[0], new_path, parts[2]);
return parse_http_request(&new_request);
} }
if parts.len() >= 3 {
let request = HttpRequest { let request = HttpRequest {
method: parts[0].to_string(), headers,
path: parts[1].to_string(), body: if body.is_empty() { None } else { Some(body) },
version: parts[2].to_string(),
}; };
Ok(request)
} else { proxy(request, &stream);
Err("Invalid HTTP request".into())
}
} }
// FUNCTION TO PARSE HTTP REQUESTS

4
src/main.rs

@ -1,6 +1,8 @@
mod http; mod http;
mod routes;
mod models; mod models;
mod parsers;
mod routes;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {

6
src/models/HttpRequest.rs

@ -1,6 +1,6 @@
use crate::models::http_header::HttpHeader;
#[derive(Debug, Clone,)] #[derive(Debug, Clone,)]
pub struct HttpRequest { pub struct HttpRequest {
pub method: String, pub headers: HttpHeader,
pub path: String, pub body: Option<String>
pub version: String,
} }

9
src/models/http_header.rs

@ -0,0 +1,9 @@
#[derive(Debug, Clone)]
pub struct HttpHeader {
pub method: Option<String>,
pub path: Option<Vec<String>>,
pub version: Option<String>,
pub content_length: Option<usize>,
pub content_type: Option<String>,
}

3
src/models/mod.rs

@ -1 +1,4 @@
pub mod HttpRequest; pub mod HttpRequest;
pub mod http_header;
pub mod user;

9
src/models/user.rs

@ -0,0 +1,9 @@
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct User {
pub id: Option<i32>,
pub username: String,
pub email: String,
pub password: String
}

64
src/parsers.rs

@ -0,0 +1,64 @@
use crate::models::http_header::HttpHeader;
pub fn parse_headers(header_lines: Vec<String>) -> HttpHeader {
let mut headers = HttpHeader {
method: None,
content_length: None,
content_type: None,
path: None,
version: None,
};
let mut iter = 0;
for line in header_lines {
println!("Header line: {}", line);
if iter == 0 {
// Parse request line
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() == 3 {
headers.method = Some(parts[0].to_string());
headers.path = Some(parts[1].split("/").map(|s| s.to_string()).collect());
headers.version = Some(parts[2].to_string());
} else {
println!("Invalid request line: {}", line);
}
iter = iter + 1;
continue;
} else {
let splitted: Vec<&str> = line.split(":").collect();
if splitted.len() != 2 {
println!("Invalid header line: {}", line);
continue;
}
match splitted[0].to_lowercase().as_str() {
"content-length" => {
if let Ok(length) = splitted[1].trim().parse::<usize>() {
headers.content_length = Some(length);
}
}
"content-type" => {
headers.content_type = Some(splitted[1].trim().to_string());
}
_ => {
println!("Unknown header: {}", splitted[0]);
}
}
println!("Parsed header: {:?}", headers);
}
iter = iter + 1;
}
headers
}
pub fn parse_json_body<T: serde::de::DeserializeOwned>(body: &str) -> Result<T, serde_json::Error> {
serde_json::from_str(body)
}

46
src/routes/auth.rs

@ -1,19 +1,17 @@
use crate::models::HttpRequest::HttpRequest; use crate::models::{HttpRequest::HttpRequest, user::User};
use crate::parsers::parse_json_body;
use std::net::TcpStream; use std::net::TcpStream;
use std::io::prelude::*; use std::io::prelude::*;
pub fn handle_auth_request(http_request: HttpRequest, mut stream: &TcpStream) { pub fn handle_auth_request(http_request: HttpRequest, mut stream: &TcpStream) {
match http_request.headers.method.as_ref().expect("").as_str() {
match http_request.method.as_str() {
"POST" => { "POST" => {
// Handle login or registration // Handle login or registration
println!("Handling auth POST request for path: {}", http_request.path); post(http_request, &stream);
let response = "HTTP/1.1 200 OK\r\n\r\nAuth POST Response";
stream.write_all(response.as_bytes()).unwrap();
} }
"GET" => { "GET" => {
// Handle fetching user info or logout // Handle fetching user info or logout
println!("Handling auth GET request for path: {}", http_request.path); println!("Handling auth GET request for path: {}", http_request.headers.path.as_ref().expect("").join("/"));
let response = "HTTP/1.1 200 OK\r\n\r\nAuth GET Response"; let response = "HTTP/1.1 200 OK\r\n\r\nAuth GET Response";
stream.write_all(response.as_bytes()).unwrap(); stream.write_all(response.as_bytes()).unwrap();
} }
@ -25,3 +23,37 @@ pub fn handle_auth_request(http_request: HttpRequest, mut stream: &TcpStream) {
} }
} }
} }
pub fn post(http_request: HttpRequest, mut stream: &TcpStream) {
println!("Handling auth POST request: {}", http_request.headers.path.as_ref().expect("").join("/"));
match http_request.headers.path.clone().expect("").join("/").as_str() {
"/api/auth/login" => {
println!("Handling login");
let response = "HTTP/1.1 200 OK\r\n\r\nLogin Successful";
stream.write_all(response.as_bytes()).unwrap();
}
"/api/auth/register" => {
register(http_request, &stream);
}
_ => {
let response = "HTTP/1.1 404 NOT FOUND\r\n\r\nAuth Endpoint Not Found";
stream.write_all(response.as_bytes()).unwrap();
}
}
}
pub fn register(http_request: HttpRequest, mut stream: &TcpStream) {
let response = "HTTP/1.1 200 OK\r\n\r\nRegistration Successful";
if http_request.headers.content_type.as_ref().expect("") != "application/json" {
let response = "HTTP/1.1 400 BAD REQUEST\r\n\r\nContent-Type must be application/json";
stream.write_all(response.as_bytes()).unwrap();
return;
}
let user = parse_json_body::<User>(&http_request.body.expect("")).unwrap();
println!("Registered user: {}", user.username);
stream.write_all(response.as_bytes()).unwrap();
}

12
src/routes/proxy.rs

@ -10,10 +10,7 @@ fn is_file(file: &str) -> bool {
pub fn proxy(http_request: HttpRequest, stream: &TcpStream) { pub fn proxy(http_request: HttpRequest, stream: &TcpStream) {
println!("Proxying request..."); println!("Proxying request...");
let path = &http_request.path; if is_file(http_request.clone().headers.path.expect("").join("/").as_str()) {
let parsed_path = path.trim_start_matches('/').split('/').collect::<Vec<&str>>();
let target = parsed_path[parsed_path.len() -1];
if is_file(target) {
statics::serve_static_file(http_request.clone(), stream); statics::serve_static_file(http_request.clone(), stream);
} else { } else {
handle_api_request(http_request.clone(), stream); handle_api_request(http_request.clone(), stream);
@ -22,11 +19,10 @@ pub fn proxy(http_request: HttpRequest, stream: &TcpStream) {
pub fn handle_api_request(http_request: HttpRequest, stream: &TcpStream) { pub fn handle_api_request(http_request: HttpRequest, stream: &TcpStream) {
let parsed_path = http_request.path.trim_start_matches('/').split('/').collect::<Vec<&str>>(); println!("{:?}", http_request.headers.path.clone().expect("")[0].as_str());
match http_request.headers.path.clone().expect("")[1].as_str() {
match parsed_path[0] {
"api" => { "api" => {
match parsed_path[1] { match http_request.headers.path.clone().expect("")[2].as_str() {
"auth" => auth::handle_auth_request(http_request, &stream), "auth" => auth::handle_auth_request(http_request, &stream),
_ => (), _ => (),
} }

2
src/routes/statics.rs

@ -5,7 +5,7 @@ use crate::models::HttpRequest::HttpRequest;
pub fn serve_static_file(file_path: HttpRequest, mut stream: &TcpStream) { pub fn serve_static_file(file_path: HttpRequest, mut stream: &TcpStream) {
let file = std::fs::read_to_string(format!("./static/{}", file_path.path.trim_start_matches('/'))); let file = std::fs::read_to_string(format!("./static/{}", file_path.headers.path.expect("").join("/").trim_start_matches('/')));
let response = match file { let response = match file {
Ok(contents) => { Ok(contents) => {

7
src/utils/db.rs

@ -0,0 +1,7 @@
use sqlx;
pub async fn establish_connection() -> Result<PgPool, sqlx::Error> {
let database_url = "non";
let pool = PgPool::connect(database_url).await?;
Ok(pool)
}
Loading…
Cancel
Save