Skip to content

Commit 7acef17

Browse files
committed
Add the book c20 building a multithreaded web server
1 parent 6a89e01 commit 7acef17

File tree

7 files changed

+188
-0
lines changed

7 files changed

+188
-0
lines changed

README.org

+1
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,4 @@ The official [[https://github.com/tokio-rs/tokio][Tokio]] (asynchronous runtime
6161
The official rust book - [[https://github.com/rust-lang/book][The Rust Programming Language]].
6262

6363
- [[file:the-book/minigrep/src/main.rs][chapter 12: building a command line program]]
64+
- [[file:the-book/hello/src/bin/main.rs][chapter 20: building a multithreaded web server]]

the-book/hello/404.html

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<!doctype html>
2+
<html class="no-js" lang="">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta http-equiv="x-ua-compatible" content="ie=edge">
6+
<title>Hello!</title>
7+
<meta name="description" content="">
8+
<meta name="viewport" content="width=device-width, initial-scale=1">
9+
10+
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
11+
<!-- Place favicon.ico in the root directory -->
12+
13+
</head>
14+
<body>
15+
<h1>Oops!</h1>
16+
<p>Sorry, I don't know what you're asking for.</p>
17+
</body>
18+
</html>

the-book/hello/Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

the-book/hello/Cargo.toml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "hello"
3+
version = "0.1.0"
4+
edition = "2018"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]

the-book/hello/hello.html

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<!doctype html>
2+
<html class="no-js" lang="">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta http-equiv="x-ua-compatible" content="ie=edge">
6+
<title>Hello!</title>
7+
<meta name="description" content="">
8+
<meta name="viewport" content="width=device-width, initial-scale=1">
9+
10+
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
11+
<!-- Place favicon.ico in the root directory -->
12+
13+
</head>
14+
<body>
15+
<h1>Hello!</h1>
16+
<p>Hi from Rust</p>
17+
</body>
18+
</html>

the-book/hello/src/bin/main.rs

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use std::fs;
2+
use std::io::prelude::*;
3+
use std::net::TcpListener;
4+
use std::net::TcpStream;
5+
use std::thread;
6+
use std::time::Duration;
7+
8+
use hello::ThreadPool;
9+
10+
fn main() {
11+
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
12+
let pool = ThreadPool::new(4);
13+
14+
for stream in listener.incoming().take(2) {
15+
let stream = stream.unwrap();
16+
17+
pool.execute(|| {
18+
handle_connection(stream);
19+
});
20+
}
21+
22+
println!("Shutting down.");
23+
}
24+
25+
fn handle_connection(mut stream: TcpStream) {
26+
let mut buffer = [0; 1024];
27+
stream.read(&mut buffer).unwrap();
28+
29+
let get = b"GET / HTTP/1.1\r\n";
30+
let sleep = b"GET /sleep HTTP/1.1\r\n";
31+
32+
let (status_line, filename) = if buffer.starts_with(get) {
33+
("HTTP/1.1 200 OK", "hello.html")
34+
} else if buffer.starts_with(sleep) {
35+
thread::sleep(Duration::from_secs(5));
36+
("HTTP/1.1 200 OK", "hello.html")
37+
} else {
38+
("HTTP/1.1 404 NOT FOUND", "404.html")
39+
};
40+
41+
let contents = fs::read_to_string(filename).unwrap();
42+
43+
let response = format!(
44+
"{}\r\nContent-Length: {}\r\n\r\n{}",
45+
status_line,
46+
contents.len(),
47+
contents
48+
);
49+
50+
stream.write(response.as_bytes()).unwrap();
51+
stream.flush().unwrap();
52+
}

the-book/hello/src/lib.rs

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use std::sync::mpsc;
2+
use std::sync::Arc;
3+
use std::sync::Mutex;
4+
use std::thread;
5+
6+
type Job = Box<dyn FnOnce() + Send + 'static>;
7+
8+
enum Message {
9+
NewJob(Job),
10+
Terminate,
11+
}
12+
13+
struct Worker {
14+
id: usize,
15+
thread: Option<thread::JoinHandle<()>>,
16+
}
17+
18+
impl Worker {
19+
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Message>>>) -> Self {
20+
let thread = thread::spawn(move || loop {
21+
let message = receiver.lock().unwrap().recv().unwrap();
22+
match message {
23+
Message::NewJob(job) => {
24+
println!("Worker {} got job; executing.", id);
25+
job();
26+
}
27+
Message::Terminate => {
28+
println!("Worker {} was told to terminate.", id);
29+
break;
30+
}
31+
}
32+
});
33+
34+
Self {
35+
id,
36+
thread: Some(thread),
37+
}
38+
}
39+
}
40+
41+
pub struct ThreadPool {
42+
workers: Vec<Worker>,
43+
sender: mpsc::Sender<Message>,
44+
}
45+
46+
impl ThreadPool {
47+
pub fn new(size: usize) -> ThreadPool {
48+
assert!(size > 0);
49+
50+
let (sender, receiver) = mpsc::channel();
51+
let receiver = Arc::new(Mutex::new(receiver));
52+
let mut workers = Vec::with_capacity(size);
53+
for id in 0..size {
54+
workers.push(Worker::new(id, Arc::clone(&receiver)));
55+
}
56+
57+
ThreadPool { workers, sender }
58+
}
59+
60+
pub fn execute<F>(&self, f: F)
61+
where
62+
F: FnOnce() + Send + 'static,
63+
{
64+
let job = Box::new(f);
65+
self.sender.send(Message::NewJob(job)).unwrap();
66+
}
67+
}
68+
69+
impl Drop for ThreadPool {
70+
fn drop(&mut self) {
71+
println!("Sending terminate message to all workers");
72+
for _ in &self.workers {
73+
self.sender.send(Message::Terminate).unwrap();
74+
}
75+
76+
println!("Shutting down all workers");
77+
for worker in &mut self.workers {
78+
println!("Shutting down worker {}", worker.id);
79+
if let Some(thread) = worker.thread.take() {
80+
thread.join().unwrap();
81+
}
82+
}
83+
}
84+
}

0 commit comments

Comments
 (0)