Skip to content

Instantly share code, notes, and snippets.

@lmllrjr
Last active June 4, 2023 00:38
Show Gist options
  • Save lmllrjr/fca238689a7f1fa830dab16ee3860bf1 to your computer and use it in GitHub Desktop.
Save lmllrjr/fca238689a7f1fa830dab16ee3860bf1 to your computer and use it in GitHub Desktop.
Rust server with regex-table routing and basic auth.
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: &regex::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: &regex::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: &regex::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, &regex::Captures<'_>, &Vec<String>),
}
fn new_route(
regex: &str,
handler_func: fn(&TcpStream, &regex::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