init
This commit is contained in:
commit
02acb04f65
9 changed files with 582 additions and 0 deletions
116
src/lib.rs
Normal file
116
src/lib.rs
Normal file
|
@ -0,0 +1,116 @@
|
|||
use std::sync::{Arc, Mutex, mpsc};
|
||||
use std::thread;
|
||||
|
||||
pub enum HTTP_RESPONSE_CODE {
|
||||
HTTP_200_OK,
|
||||
HTTP_400_BAD_REQUEST,
|
||||
HTTP_404_NOT_FOUND,
|
||||
HTTP_501_NOT_IMPLEMENTED,
|
||||
HTTP_505_HTTP_VERSION_NOT_SUPPORTED,
|
||||
}
|
||||
impl std::fmt::Debug for HTTP_RESPONSE_CODE {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
match self {
|
||||
HTTP_RESPONSE_CODE::HTTP_200_OK => write!(f, "HTTP_200_OK"),
|
||||
HTTP_RESPONSE_CODE::HTTP_400_BAD_REQUEST => write!(f, "HTTP_400_BAD_REQUEST"),
|
||||
HTTP_RESPONSE_CODE::HTTP_404_NOT_FOUND => write!(f, "HTTP_404_NOT_FOUND"),
|
||||
HTTP_RESPONSE_CODE::HTTP_501_NOT_IMPLEMENTED => write!(f, "HTTP_501_NOT_IMPLEMENTED"),
|
||||
HTTP_RESPONSE_CODE::HTTP_505_HTTP_VERSION_NOT_SUPPORTED => {
|
||||
write!(f, "HTTP_505_HTTP_VERSION_NOT_SUPPORTED")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for HTTP_RESPONSE_CODE {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
match self {
|
||||
HTTP_RESPONSE_CODE::HTTP_200_OK => write!(f, "200 OK"),
|
||||
HTTP_RESPONSE_CODE::HTTP_400_BAD_REQUEST => write!(f, "400 BAD REQUEST"),
|
||||
HTTP_RESPONSE_CODE::HTTP_404_NOT_FOUND => write!(f, "404 NOT FOUND"),
|
||||
HTTP_RESPONSE_CODE::HTTP_501_NOT_IMPLEMENTED => write!(f, "501 NOT IMPLEMENTED"),
|
||||
HTTP_RESPONSE_CODE::HTTP_505_HTTP_VERSION_NOT_SUPPORTED => {
|
||||
write!(f, "HTTP_505_HTTP_VERSION_NOT_SUPPORTED")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThreadPool {
|
||||
workers: Vec<Worker>,
|
||||
sender: Option<mpsc::Sender<Job>>,
|
||||
}
|
||||
impl ThreadPool {
|
||||
pub fn new(size: usize) -> ThreadPool {
|
||||
assert!(size > 0);
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
let receiver = Arc::new(Mutex::new(receiver));
|
||||
let mut workers = Vec::with_capacity(size);
|
||||
for id in 0..size {
|
||||
workers.push(Worker::new(id, Arc::clone(&receiver)));
|
||||
}
|
||||
ThreadPool {
|
||||
workers,
|
||||
sender: Some(sender),
|
||||
}
|
||||
}
|
||||
pub fn execute<F>(&self, f: F)
|
||||
where
|
||||
F: FnOnce() + Send + 'static,
|
||||
{
|
||||
let job = Box::new(f);
|
||||
match self.sender.as_ref() {
|
||||
Some(sender) => match sender.send(job) {
|
||||
Ok(_) => {}
|
||||
Err(e) => println!("[ERROR]: {e}"),
|
||||
},
|
||||
None => println!("[ERROR] no sender to execute!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Drop for ThreadPool {
|
||||
fn drop(&mut self) {
|
||||
drop(self.sender.take());
|
||||
for worker in self.workers.drain(..) {
|
||||
println!("Shutting down worker {}", worker.id);
|
||||
match worker.thread.join() {
|
||||
Ok(join) => join,
|
||||
Err(_) => println!(
|
||||
"Failed to join thread while shutting down worker {}",
|
||||
worker.id
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Job = Box<dyn FnOnce() + Send + 'static>;
|
||||
|
||||
struct Worker {
|
||||
id: usize,
|
||||
thread: thread::JoinHandle<()>,
|
||||
}
|
||||
impl Worker {
|
||||
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
|
||||
let thread = thread::spawn(move || {
|
||||
loop {
|
||||
match receiver.lock() {
|
||||
Ok(receiver) => {
|
||||
let message = receiver.recv();
|
||||
match message {
|
||||
Ok(job) => {
|
||||
println!("Worker {id} got a job, executing.");
|
||||
job();
|
||||
}
|
||||
Err(_) => {
|
||||
println!("Worker {id} disconnected, shutting down.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => println!("[ERROR]: {e}"),
|
||||
}
|
||||
}
|
||||
});
|
||||
Worker { id, thread }
|
||||
}
|
||||
}
|
90
src/main.rs
Normal file
90
src/main.rs
Normal file
|
@ -0,0 +1,90 @@
|
|||
use clap::Parser;
|
||||
use regex::Regex;
|
||||
use rust_http_server::HTTP_RESPONSE_CODE::*;
|
||||
use rust_http_server::*;
|
||||
use std::fs;
|
||||
use std::io::{BufReader, prelude::*};
|
||||
use std::net::{SocketAddr, TcpListener, TcpStream};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Args {
|
||||
#[arg(short, long, default_value_t = 8383)]
|
||||
port: u16,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
let listener = TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], args.port)))
|
||||
.expect(&format!("Failed to bind to port {}", args.port));
|
||||
println!("Successfully started server at port {}", args.port);
|
||||
let pool = ThreadPool::new(8);
|
||||
for stream in listener.incoming() {
|
||||
pool.execute(|| {
|
||||
handle_connection(stream.unwrap());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_connection(stream: TcpStream) {
|
||||
let contents = match parse_connection_to_header(&stream) {
|
||||
Ok(request) => {
|
||||
println!("Request: {request}");
|
||||
match parse_header_to_filepath(request) {
|
||||
Ok(filepath) => read_file(filepath),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
};
|
||||
respond(&stream, contents);
|
||||
}
|
||||
|
||||
fn parse_connection_to_header(stream: &TcpStream) -> Result<String, HTTP_RESPONSE_CODE> {
|
||||
// read first line of buffer
|
||||
let buf_reader = BufReader::new(stream);
|
||||
match buf_reader.lines() {
|
||||
mut lines => match lines.next() {
|
||||
Some(Ok(line)) => Ok(line),
|
||||
_ => Err(HTTP_400_BAD_REQUEST),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_header_to_filepath(request: String) -> Result<String, HTTP_RESPONSE_CODE> {
|
||||
let regex = Regex::new(r"GET /(?<path>[^/].*|) HTTP/1\.1").unwrap();
|
||||
match regex.captures(&request) {
|
||||
Some(captures) => Ok(captures["path"].to_string()),
|
||||
None => Err(HTTP_400_BAD_REQUEST),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_file(filename: String) -> Result<String, HTTP_RESPONSE_CODE> {
|
||||
match fs::read_to_string(if filename == "" {
|
||||
"index.html"
|
||||
} else {
|
||||
&filename
|
||||
}) {
|
||||
Ok(file) => Ok(file),
|
||||
Err(_) => Err(HTTP_404_NOT_FOUND),
|
||||
}
|
||||
}
|
||||
|
||||
fn respond(mut stream: &TcpStream, contents: Result<String, HTTP_RESPONSE_CODE>) {
|
||||
let (status, length, contents) = match contents {
|
||||
Ok(contents) =>
|
||||
( format!("HTTP/1.1 {HTTP_200_OK}")
|
||||
, contents.len()
|
||||
, contents
|
||||
),
|
||||
Err(err) =>
|
||||
( format!("HTTP/1.1 {err}")
|
||||
, "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>404 NOT FOUND</title></head><body>Page not found. Check that you typed the address correctly, or contact the site owner.</body></html>".len()
|
||||
, "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>404 NOT FOUND</title></head><body>Page not found. Check that you typed the address correctly, or contact the site owner.</body></html>".to_string()
|
||||
),
|
||||
};
|
||||
println!("Response: {status}\r\nContent-Length: {length}\r\n\r\n{contents}");
|
||||
stream
|
||||
.write_all(format!("{status}\r\nContent-Length: {length}\r\n\r\n{contents}").as_bytes())
|
||||
.expect("Error writing to stream");
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue