Last active
June 4, 2023 00:38
-
-
Save lmllrjr/fca238689a7f1fa830dab16ee3860bf1 to your computer and use it in GitHub Desktop.
Rust server with regex-table routing and basic auth.
This file contains 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 base64::{engine::general_purpose, Engine as _}; | |
use regex::Regex; | |
use std::{ | |
io::{prelude::*, BufReader}, | |
net::{TcpListener, TcpStream}, | |
str, | |
}; | |
fn main() -> std::io::Result<()> { | |
let listener = TcpListener::bind("127.0.0.1:6969")?; | |
let routes: &[Route; 3] = &[ | |
new_route("GET /([^/]+)/([0-9]+) HTTP/1.1", test_slug_id), | |
new_route("GET /{1} HTTP/1.1", handler_root), | |
new_route("GET /greet/([^/]+) HTTP/1.1", handler_greet), | |
]; | |
for stream in listener.incoming() { | |
match stream { | |
Ok(stream) => { | |
router(stream, routes); | |
} | |
Err(e) => { | |
/* connection failed */ | |
println!("{}", e); | |
} | |
} | |
} | |
Ok(()) | |
} | |
fn handler_root(mut stream: &TcpStream, _caps: ®ex::Captures<'_>, req: &Vec<String>) { | |
println!("Request: {:#?}", req); | |
let status_line = "HTTP/1.1 204 NO CONTENT"; | |
let response = format!("{status_line}\r\nContent-Length: 0\r\n\r\n"); | |
stream.write_all(response.as_bytes()).unwrap(); | |
// let contents = fs::read_to_string("404.html").unwrap(); | |
// let length = contents.len(); | |
// let response = format!("HTTP/1.1 200 OK\r\nContent-Length: {length}\r\n\r\n{contents}"); | |
// stream.write_all(response.as_bytes()).unwrap(); | |
} | |
fn handler_greet(mut stream: &TcpStream, caps: ®ex::Captures<'_>, req: &Vec<String>) { | |
println!("Request: {:#?}", req); | |
let length = &(caps[1].len()) + 6; | |
let name = &caps[1]; | |
let content = format!("hello {name}"); | |
let response = format!("HTTP/1.1 200 OK\r\nContent-Length: {length}\r\n\r\n{content}"); | |
stream.write_all(response.as_bytes()).unwrap(); | |
} | |
fn test_slug_id(mut stream: &TcpStream, caps: ®ex::Captures<'_>, req: &Vec<String>) { | |
println!("Request: {:#?}", req); | |
// println!("{:?}", caps); | |
let length = &(caps[1].len() + caps[2].len()) + 5; | |
let slug = &caps[1]; | |
let id = &caps[2]; | |
let content = format!("{slug} and {id}"); | |
let response = format!("HTTP/1.1 200 OK\r\nContent-Length: {length}\r\n\r\n{content}"); | |
stream.write_all(response.as_bytes()).unwrap(); | |
} | |
struct Route { | |
regex: regex::Regex, | |
handler: fn(&TcpStream, ®ex::Captures<'_>, &Vec<String>), | |
} | |
fn new_route( | |
regex: &str, | |
handler_func: fn(&TcpStream, ®ex::Captures<'_>, &Vec<String>), | |
) -> Route { | |
let rgx_str = format!(r"{}{}{}", r"^", regex, "$"); | |
let rgx = Regex::new(&rgx_str).unwrap(); | |
Route { | |
regex: rgx, | |
handler: handler_func, | |
} | |
} | |
fn basic_auth(req: &Vec<String>) -> bool { | |
let rgx = Regex::new(r"Authorization: Basic (.*)").unwrap(); | |
for headers in req { | |
if let Some(auth) = rgx.captures(headers) { | |
let decoded_bytes = general_purpose::STANDARD.decode(&auth[1]).unwrap(); | |
let s = match str::from_utf8(&decoded_bytes) { | |
Ok(v) => v, | |
Err(e) => panic!("Invalid UTF-8 sequence: {}", e), | |
}; | |
if s == "007:123" { | |
return true; | |
} | |
}; | |
} | |
false | |
} | |
/// router handles the multi plexing of a request stream | |
/// by a given table of routes. | |
fn router(mut stream: TcpStream, routes: &[Route]) { | |
// let buf_reader = BufReader::new(stream); | |
// let request_line = buf_reader.lines().next().unwrap().unwrap(); | |
let buf_reader = BufReader::new(&mut stream); | |
let http_request: Vec<_> = buf_reader | |
.lines() | |
.map(|result| result.unwrap()) | |
.take_while(|line| !line.is_empty()) | |
.collect(); | |
let auth = basic_auth(&http_request); | |
if !auth { | |
let status_line = "HTTP/1.1 401 UNAUTHORIZED"; | |
let content = "{\"err\":\"401 unathorized\"}"; | |
let length = content.len(); | |
let response = format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{content}"); | |
stream.write_all(response.as_bytes()).unwrap(); | |
return; | |
} | |
for route in routes { | |
if let Some(caps) = route.regex.captures(&http_request[0]) { | |
(route.handler)(&mut stream, &caps, &http_request); | |
return; | |
}; | |
} | |
// handle not found | |
let status_line = "HTTP/1.1 404 NOT FOUND"; | |
let content = "{\"err\":\"404 not found\"}"; | |
let length = content.len(); | |
let response = format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{content}"); | |
stream.write_all(response.as_bytes()).unwrap(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment