initial commit

This commit is contained in:
Noah Swerhun 2023-07-03 16:44:57 -05:00
parent 4f6f5c3373
commit 943697af65
9 changed files with 231 additions and 1 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

7
Cargo.lock generated Normal file
View file

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "rust-webserver"
version = "0.1.0"

14
Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "rust-webserver"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
[profile.release]
lto = true
strip = true
panic = "abort"
codegen-units = 1

View file

@ -1,3 +1,13 @@
# rust-webserver
Very basic multithreaded TCP server in Rust
Very basic multithreaded TCP server in Rust.
See `src/main.rs`. Server will listen for a certain number of connections on a
given TCP port, and then gracefully exit.
This project is taken from The Rust Book.
**Run:**
```
cargo run --release
```

11
html/404.html Normal file
View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error!</title>
</head>
<body>
<h1>Oops!</h1>
<p>Sorry, I don't know what you're asking for.</p>
</body>
</html>

11
html/index.html Normal file
View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Welcome!</title>
</head>
<body>
<h1>Hello!</h1>
<p>Hi from Rust</p>
</body>
</html>

11
html/slow.html Normal file
View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>That took a while</title>
</head>
<body>
<h1>Hi</h1>
<p>You just waited 5secs to see this page.</p>
</body>
</html>

103
src/lib.rs Normal file
View file

@ -0,0 +1,103 @@
use std::{
fmt::Display,
thread,
error::Error,
sync::{
mpsc,
Arc,
Mutex,
},
};
pub struct ThreadPool {
workers: Vec<Worker>,
sender: Option<mpsc::Sender<Job>>,
}
type Job = Box<dyn FnOnce() + Send + 'static>;
struct Worker {
id: usize,
thread: Option<thread::JoinHandle<()>>,
}
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
Worker { id, thread: Some(thread::spawn(move || {
loop {
match (*receiver).lock().unwrap().recv() {
Ok(job) => {
println!("Worker {} got a job... running.", id);
job();
},
Err(_) => {
println!("Worker {} stopping.", id);
break;
},
}
}
}))}
}
}
#[derive(Debug)]
pub enum ThreadPoolError {
ZeroSize
}
impl Error for ThreadPoolError {
fn description(&self) -> &str {
match *self {
ThreadPoolError::ZeroSize => "ZeroPoolSize",
}
}
}
impl Display for ThreadPoolError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
ThreadPoolError::ZeroSize =>
f.write_str("Thread pool size is zero"),
}
}
}
impl ThreadPool {
pub fn new(size: usize) -> Result<ThreadPool, ThreadPoolError> {
if size == 0 {
return Err(ThreadPoolError::ZeroSize);
}
let mut workers: Vec<Worker> = Vec::with_capacity(size);
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
for i in 0..size {
workers.push(Worker::new(i, Arc::clone(&receiver)));
}
Ok(ThreadPool { workers, sender: Some(sender) })
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static
{
let job = Box::new(f);
(*self).sender.as_ref().unwrap().send(job).unwrap();
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
drop((*self).sender.take());
for worker in &mut (*self).workers {
println!("Shutting down worker {}.", worker.id);
if let Some(thread) = worker.thread.take() {
thread.join().unwrap();
}
}
}
}

62
src/main.rs Normal file
View file

@ -0,0 +1,62 @@
use std::{
net::{TcpListener, TcpStream},
io::{prelude::*, BufReader},
fs,
thread,
time::Duration,
};
use rust_webserver::ThreadPool;
fn parse_request(stream: &TcpStream) -> Vec<String>{
let buf_reader = BufReader::new(stream);
buf_reader
.lines()
.map(|result| result.unwrap())
.take_while(|line| !line.is_empty())
.collect()
}
fn generate_reposne(request: &Vec<String>) -> String {
let request_header = (*(request.first().unwrap())).clone();
let (status, file) =
match &request_header[..] {
"GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "html/index.html"),
"GET /sleep HTTP/1.1" => {
thread::sleep(Duration::from_secs(5));
("HTTP/1.1 200 OK", "html/slow.html")
},
_ => ("HTTP/1.1 404 NOT FOUND", "html/404.html")
};
let contents = fs::read_to_string(file).unwrap();
let len = contents.len();
format!("{status}\r\nContent-Length: {len}\r\n\r\n{contents}")
}
fn handle_connection(mut stream: TcpStream) {
let request = parse_request(&stream);
let response = generate_reposne(&request);
stream.write_all(response.as_bytes()).unwrap();
}
fn main() {
let port = 7878;
let connections = 3;
let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
let pool = ThreadPool::new(10).unwrap();
println!("Listening for {} connnections on port {}", connections, port);
for stream in listener.incoming().take(connections) {
let stream = stream.unwrap();
pool.execute(|| {
handle_connection(stream);
});
}
}