feat: added cleanscript
This commit is contained in:
43
ch21/ch21-00-final-project-a-web-server.html
Normal file
43
ch21/ch21-00-final-project-a-web-server.html
Normal file
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Final Project: Building a Multithreaded Web Server</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="final-project-building-a-multithreaded-web-server"><a class="header" href="#final-project-building-a-multithreaded-web-server">Final Project: Building a Multithreaded Web Server</a></h1>
|
||||
<p>It’s been a long journey, but we’ve reached the end of the book. In this
|
||||
chapter, we’ll build one more project together to demonstrate some of the
|
||||
concepts we covered in the final chapters, as well as recap some earlier
|
||||
lessons.</p>
|
||||
<p>For our final project, we’ll make a web server that says “Hello!” and looks like
|
||||
Figure 21-1 in a web browser.</p>
|
||||
<p>Here is our plan for building the web server:</p>
|
||||
<ol>
|
||||
<li>Learn a bit about TCP and HTTP.</li>
|
||||
<li>Listen for TCP connections on a socket.</li>
|
||||
<li>Parse a small number of HTTP requests.</li>
|
||||
<li>Create a proper HTTP response.</li>
|
||||
<li>Improve the throughput of our server with a thread pool.</li>
|
||||
</ol>
|
||||
<img alt="Screenshot of a web browser visiting the address 127.0.0.1:8080 displaying a webpage with the text content “Hello! Hi from Rust”" src="../img/trpl21-01.png" class="center" style="width: 50%;" />
|
||||
<p><span class="caption">Figure 21-1: Our final shared project</span></p>
|
||||
<p>Before we get started, we should mention two details. First, the method we’ll
|
||||
use won’t be the best way to build a web server with Rust. Community members
|
||||
have published a number of production-ready crates available at
|
||||
<a href="https://crates.io/">crates.io</a> that provide more complete web server and
|
||||
thread pool implementations than we’ll build. However, our intention in this
|
||||
chapter is to help you learn, not to take the easy route. Because Rust is a
|
||||
systems programming language, we can choose the level of abstraction we want to
|
||||
work with and can go to a lower level than is possible or practical in other
|
||||
languages.</p>
|
||||
<p>Second, we will not be using async and await here. Building a thread pool is a
|
||||
big enough challenge on its own, without adding in building an async runtime!
|
||||
However, we will note how async and await might be applicable to some of the
|
||||
same problems we will see in this chapter. Ultimately, as we noted back in
|
||||
Chapter 17, many async runtimes use thread pools for managing their work.</p>
|
||||
<p>We’ll therefore write the basic HTTP server and thread pool manually so that
|
||||
you can learn the general ideas and techniques behind the crates you might use
|
||||
in the future.</p>
|
||||
</body>
|
||||
</html>
|
||||
598
ch21/ch21-01-single-threaded.html
Normal file
598
ch21/ch21-01-single-threaded.html
Normal file
@@ -0,0 +1,598 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Building a Single-Threaded Web Server</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2 id="building-a-single-threaded-web-server"><a class="header" href="#building-a-single-threaded-web-server">Building a Single-Threaded Web Server</a></h2>
|
||||
<p>We’ll start by getting a single-threaded web server working. Before we begin,
|
||||
let’s look at a quick overview of the protocols involved in building web
|
||||
servers. The details of these protocols are beyond the scope of this book, but
|
||||
a brief overview will give you the information you need.</p>
|
||||
<p>The two main protocols involved in web servers are <em>Hypertext Transfer
|
||||
Protocol</em> <em>(HTTP)</em> and <em>Transmission Control Protocol</em> <em>(TCP)</em>. Both protocols
|
||||
are <em>request-response</em> protocols, meaning a <em>client</em> initiates requests and a
|
||||
<em>server</em> listens to the requests and provides a response to the client. The
|
||||
contents of those requests and responses are defined by the protocols.</p>
|
||||
<p>TCP is the lower-level protocol that describes the details of how information
|
||||
gets from one server to another but doesn’t specify what that information is.
|
||||
HTTP builds on top of TCP by defining the contents of the requests and
|
||||
responses. It’s technically possible to use HTTP with other protocols, but in
|
||||
the vast majority of cases, HTTP sends its data over TCP. We’ll work with the
|
||||
raw bytes of TCP and HTTP requests and responses.</p>
|
||||
<h3 id="listening-to-the-tcp-connection"><a class="header" href="#listening-to-the-tcp-connection">Listening to the TCP Connection</a></h3>
|
||||
<p>Our web server needs to listen to a TCP connection, so that’s the first part
|
||||
we’ll work on. The standard library offers a <code>std::net</code> module that lets us do
|
||||
this. Let’s make a new project in the usual fashion:</p>
|
||||
<pre><code class="language-console">$ cargo new hello
|
||||
Created binary (application) `hello` project
|
||||
$ cd hello
|
||||
</code></pre>
|
||||
<p>Now enter the code in Listing 21-1 in <em>src/main.rs</em> to start. This code will
|
||||
listen at the local address <code>127.0.0.1:7878</code> for incoming TCP streams. When it
|
||||
gets an incoming stream, it will print <code>Connection established!</code>.</p>
|
||||
<figure class="listing" id="listing-21-1">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust no_run edition2024">use std::net::TcpListener;
|
||||
|
||||
fn main() {
|
||||
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
|
||||
|
||||
for stream in listener.incoming() {
|
||||
let stream = stream.unwrap();
|
||||
|
||||
println!("Connection established!");
|
||||
}
|
||||
}</code></pre>
|
||||
<figcaption><a href="#listing-21-1">Listing 21-1</a>: Listening for incoming streams and printing a message when we receive a stream</figcaption>
|
||||
</figure>
|
||||
<p>Using <code>TcpListener</code>, we can listen for TCP connections at the address
|
||||
<code>127.0.0.1:7878</code>. In the address, the section before the colon is an IP address
|
||||
representing your computer (this is the same on every computer and doesn’t
|
||||
represent the authors’ computer specifically), and <code>7878</code> is the port. We’ve
|
||||
chosen this port for two reasons: HTTP isn’t normally accepted on this port, so
|
||||
our server is unlikely to conflict with any other web server you might have
|
||||
running on your machine, and 7878 is <em>rust</em> typed on a telephone.</p>
|
||||
<p>The <code>bind</code> function in this scenario works like the <code>new</code> function in that it
|
||||
will return a new <code>TcpListener</code> instance. The function is called <code>bind</code>
|
||||
because, in networking, connecting to a port to listen to is known as “binding
|
||||
to a port.”</p>
|
||||
<p>The <code>bind</code> function returns a <code>Result<T, E></code>, which indicates that it’s
|
||||
possible for binding to fail, for example, if we ran two instances of our
|
||||
program and so had two programs listening to the same port. Because we’re
|
||||
writing a basic server just for learning purposes, we won’t worry about
|
||||
handling these kinds of errors; instead, we use <code>unwrap</code> to stop the program if
|
||||
errors happen.</p>
|
||||
<p>The <code>incoming</code> method on <code>TcpListener</code> returns an iterator that gives us a
|
||||
sequence of streams (more specifically, streams of type <code>TcpStream</code>). A single
|
||||
<em>stream</em> represents an open connection between the client and the server.
|
||||
<em>Connection</em> is the name for the full request and response process in which a
|
||||
client connects to the server, the server generates a response, and the server
|
||||
closes the connection. As such, we will read from the <code>TcpStream</code> to see what
|
||||
the client sent and then write our response to the stream to send data back to
|
||||
the client. Overall, this <code>for</code> loop will process each connection in turn and
|
||||
produce a series of streams for us to handle.</p>
|
||||
<p>For now, our handling of the stream consists of calling <code>unwrap</code> to terminate
|
||||
our program if the stream has any errors; if there aren’t any errors, the
|
||||
program prints a message. We’ll add more functionality for the success case in
|
||||
the next listing. The reason we might receive errors from the <code>incoming</code> method
|
||||
when a client connects to the server is that we’re not actually iterating over
|
||||
connections. Instead, we’re iterating over <em>connection attempts</em>. The
|
||||
connection might not be successful for a number of reasons, many of them
|
||||
operating system specific. For example, many operating systems have a limit to
|
||||
the number of simultaneous open connections they can support; new connection
|
||||
attempts beyond that number will produce an error until some of the open
|
||||
connections are closed.</p>
|
||||
<p>Let’s try running this code! Invoke <code>cargo run</code> in the terminal and then load
|
||||
<em>127.0.0.1:7878</em> in a web browser. The browser should show an error message
|
||||
like “Connection reset” because the server isn’t currently sending back any
|
||||
data. But when you look at your terminal, you should see several messages that
|
||||
were printed when the browser connected to the server!</p>
|
||||
<pre><code class="language-text"> Running `target/debug/hello`
|
||||
Connection established!
|
||||
Connection established!
|
||||
Connection established!
|
||||
</code></pre>
|
||||
<p>Sometimes you’ll see multiple messages printed for one browser request; the
|
||||
reason might be that the browser is making a request for the page as well as a
|
||||
request for other resources, like the <em>favicon.ico</em> icon that appears in the
|
||||
browser tab.</p>
|
||||
<p>It could also be that the browser is trying to connect to the server multiple
|
||||
times because the server isn’t responding with any data. When <code>stream</code> goes out
|
||||
of scope and is dropped at the end of the loop, the connection is closed as
|
||||
part of the <code>drop</code> implementation. Browsers sometimes deal with closed
|
||||
connections by retrying, because the problem might be temporary.</p>
|
||||
<p>Browsers also sometimes open multiple connections to the server without sending
|
||||
any requests so that if they <em>do</em> later send requests, those requests can
|
||||
happen more quickly. When this occurs, our server will see each connection,
|
||||
regardless of whether there are any requests over that connection. Many
|
||||
versions of Chrome-based browsers do this, for example; you can disable that
|
||||
optimization by using private browsing mode or using a different browser.</p>
|
||||
<p>The important factor is that we’ve successfully gotten a handle to a TCP
|
||||
connection!</p>
|
||||
<p>Remember to stop the program by pressing <kbd>ctrl</kbd>-<kbd>C</kbd> when
|
||||
you’re done running a particular version of the code. Then, restart the program
|
||||
by invoking the <code>cargo run</code> command after you’ve made each set of code changes
|
||||
to make sure you’re running the newest code.</p>
|
||||
<h3 id="reading-the-request"><a class="header" href="#reading-the-request">Reading the Request</a></h3>
|
||||
<p>Let’s implement the functionality to read the request from the browser! To
|
||||
separate the concerns of first getting a connection and then taking some action
|
||||
with the connection, we’ll start a new function for processing connections. In
|
||||
this new <code>handle_connection</code> function, we’ll read data from the TCP stream and
|
||||
print it so that we can see the data being sent from the browser. Change the
|
||||
code to look like Listing 21-2.</p>
|
||||
<figure class="listing" id="listing-21-2">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust no_run edition2024">use std::{
|
||||
io::{BufReader, prelude::*},
|
||||
net::{TcpListener, TcpStream},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
|
||||
|
||||
for stream in listener.incoming() {
|
||||
let stream = stream.unwrap();
|
||||
|
||||
handle_connection(stream);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_connection(mut stream: TcpStream) {
|
||||
let buf_reader = BufReader::new(&stream);
|
||||
let http_request: Vec<_> = buf_reader
|
||||
.lines()
|
||||
.map(|result| result.unwrap())
|
||||
.take_while(|line| !line.is_empty())
|
||||
.collect();
|
||||
|
||||
println!("Request: {http_request:#?}");
|
||||
}</code></pre>
|
||||
<figcaption><a href="#listing-21-2">Listing 21-2</a>: Reading from the <code>TcpStream</code> and printing the data</figcaption>
|
||||
</figure>
|
||||
<p>We bring <code>std::io::BufReader</code> and <code>std::io::prelude</code> into scope to get access
|
||||
to traits and types that let us read from and write to the stream. In the <code>for</code>
|
||||
loop in the <code>main</code> function, instead of printing a message that says we made a
|
||||
connection, we now call the new <code>handle_connection</code> function and pass the
|
||||
<code>stream</code> to it.</p>
|
||||
<p>In the <code>handle_connection</code> function, we create a new <code>BufReader</code> instance that
|
||||
wraps a reference to the <code>stream</code>. The <code>BufReader</code> adds buffering by managing
|
||||
calls to the <code>std::io::Read</code> trait methods for us.</p>
|
||||
<p>We create a variable named <code>http_request</code> to collect the lines of the request
|
||||
the browser sends to our server. We indicate that we want to collect these
|
||||
lines in a vector by adding the <code>Vec<_></code> type annotation.</p>
|
||||
<p><code>BufReader</code> implements the <code>std::io::BufRead</code> trait, which provides the <code>lines</code>
|
||||
method. The <code>lines</code> method returns an iterator of <code>Result<String, std::io::Error></code> by splitting the stream of data whenever it sees a newline
|
||||
byte. To get each <code>String</code>, we <code>map</code> and <code>unwrap</code> each <code>Result</code>. The <code>Result</code>
|
||||
might be an error if the data isn’t valid UTF-8 or if there was a problem
|
||||
reading from the stream. Again, a production program should handle these errors
|
||||
more gracefully, but we’re choosing to stop the program in the error case for
|
||||
simplicity.</p>
|
||||
<p>The browser signals the end of an HTTP request by sending two newline
|
||||
characters in a row, so to get one request from the stream, we take lines until
|
||||
we get a line that is the empty string. Once we’ve collected the lines into the
|
||||
vector, we’re printing them out using pretty debug formatting so that we can
|
||||
take a look at the instructions the web browser is sending to our server.</p>
|
||||
<p>Let’s try this code! Start the program and make a request in a web browser
|
||||
again. Note that we’ll still get an error page in the browser, but our
|
||||
program’s output in the terminal will now look similar to this:</p>
|
||||
<!-- manual-regeneration
|
||||
cd listings/ch21-web-server/listing-21-02
|
||||
cargo run
|
||||
make a request to 127.0.0.1:7878
|
||||
Can't automate because the output depends on making requests
|
||||
-->
|
||||
<pre><code class="language-console">$ cargo run
|
||||
Compiling hello v0.1.0 (file:///projects/hello)
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.42s
|
||||
Running `target/debug/hello`
|
||||
Request: [
|
||||
"GET / HTTP/1.1",
|
||||
"Host: 127.0.0.1:7878",
|
||||
"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:99.0) Gecko/20100101 Firefox/99.0",
|
||||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||||
"Accept-Language: en-US,en;q=0.5",
|
||||
"Accept-Encoding: gzip, deflate, br",
|
||||
"DNT: 1",
|
||||
"Connection: keep-alive",
|
||||
"Upgrade-Insecure-Requests: 1",
|
||||
"Sec-Fetch-Dest: document",
|
||||
"Sec-Fetch-Mode: navigate",
|
||||
"Sec-Fetch-Site: none",
|
||||
"Sec-Fetch-User: ?1",
|
||||
"Cache-Control: max-age=0",
|
||||
]
|
||||
</code></pre>
|
||||
<p>Depending on your browser, you might get slightly different output. Now that
|
||||
we’re printing the request data, we can see why we get multiple connections
|
||||
from one browser request by looking at the path after <code>GET</code> in the first line
|
||||
of the request. If the repeated connections are all requesting <em>/</em>, we know the
|
||||
browser is trying to fetch <em>/</em> repeatedly because it’s not getting a response
|
||||
from our program.</p>
|
||||
<p>Let’s break down this request data to understand what the browser is asking of
|
||||
our program.</p>
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<p><a id="a-closer-look-at-an-http-request"></a>
|
||||
<a id="looking-closer-at-an-http-request"></a></p>
|
||||
<h3 id="looking-more-closely-at-an-http-request"><a class="header" href="#looking-more-closely-at-an-http-request">Looking More Closely at an HTTP Request</a></h3>
|
||||
<p>HTTP is a text-based protocol, and a request takes this format:</p>
|
||||
<pre><code class="language-text">Method Request-URI HTTP-Version CRLF
|
||||
headers CRLF
|
||||
message-body
|
||||
</code></pre>
|
||||
<p>The first line is the <em>request line</em> that holds information about what the
|
||||
client is requesting. The first part of the request line indicates the method
|
||||
being used, such as <code>GET</code> or <code>POST</code>, which describes how the client is making
|
||||
this request. Our client used a <code>GET</code> request, which means it is asking for
|
||||
information.</p>
|
||||
<p>The next part of the request line is <em>/</em>, which indicates the <em>uniform resource
|
||||
identifier</em> <em>(URI)</em> the client is requesting: A URI is almost, but not quite,
|
||||
the same as a <em>uniform resource locator</em> <em>(URL)</em>. The difference between URIs
|
||||
and URLs isn’t important for our purposes in this chapter, but the HTTP spec
|
||||
uses the term <em>URI</em>, so we can just mentally substitute <em>URL</em> for <em>URI</em> here.</p>
|
||||
<p>The last part is the HTTP version the client uses, and then the request line
|
||||
ends in a CRLF sequence. (<em>CRLF</em> stands for <em>carriage return</em> and <em>line feed</em>,
|
||||
which are terms from the typewriter days!) The CRLF sequence can also be
|
||||
written as <code>\r\n</code>, where <code>\r</code> is a carriage return and <code>\n</code> is a line feed. The
|
||||
<em>CRLF sequence</em> separates the request line from the rest of the request data.
|
||||
Note that when the CRLF is printed, we see a new line start rather than <code>\r\n</code>.</p>
|
||||
<p>Looking at the request line data we received from running our program so far,
|
||||
we see that <code>GET</code> is the method, <em>/</em> is the request URI, and <code>HTTP/1.1</code> is the
|
||||
version.</p>
|
||||
<p>After the request line, the remaining lines starting from <code>Host:</code> onward are
|
||||
headers. <code>GET</code> requests have no body.</p>
|
||||
<p>Try making a request from a different browser or asking for a different
|
||||
address, such as <em>127.0.0.1:7878/test</em>, to see how the request data changes.</p>
|
||||
<p>Now that we know what the browser is asking for, let’s send back some data!</p>
|
||||
<h3 id="writing-a-response"><a class="header" href="#writing-a-response">Writing a Response</a></h3>
|
||||
<p>We’re going to implement sending data in response to a client request.
|
||||
Responses have the following format:</p>
|
||||
<pre><code class="language-text">HTTP-Version Status-Code Reason-Phrase CRLF
|
||||
headers CRLF
|
||||
message-body
|
||||
</code></pre>
|
||||
<p>The first line is a <em>status line</em> that contains the HTTP version used in the
|
||||
response, a numeric status code that summarizes the result of the request, and
|
||||
a reason phrase that provides a text description of the status code. After the
|
||||
CRLF sequence are any headers, another CRLF sequence, and the body of the
|
||||
response.</p>
|
||||
<p>Here is an example response that uses HTTP version 1.1 and has a status code of
|
||||
200, an OK reason phrase, no headers, and no body:</p>
|
||||
<pre><code class="language-text">HTTP/1.1 200 OK\r\n\r\n
|
||||
</code></pre>
|
||||
<p>The status code 200 is the standard success response. The text is a tiny
|
||||
successful HTTP response. Let’s write this to the stream as our response to a
|
||||
successful request! From the <code>handle_connection</code> function, remove the
|
||||
<code>println!</code> that was printing the request data and replace it with the code in
|
||||
Listing 21-3.</p>
|
||||
<figure class="listing" id="listing-21-3">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust no_run edition2024"><span class="boring">use std::{
|
||||
</span><span class="boring"> io::{BufReader, prelude::*},
|
||||
</span><span class="boring"> net::{TcpListener, TcpStream},
|
||||
</span><span class="boring">};
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> for stream in listener.incoming() {
|
||||
</span><span class="boring"> let stream = stream.unwrap();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> handle_connection(stream);
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>fn handle_connection(mut stream: TcpStream) {
|
||||
let buf_reader = BufReader::new(&stream);
|
||||
let http_request: Vec<_> = buf_reader
|
||||
.lines()
|
||||
.map(|result| result.unwrap())
|
||||
.take_while(|line| !line.is_empty())
|
||||
.collect();
|
||||
|
||||
let response = "HTTP/1.1 200 OK\r\n\r\n";
|
||||
|
||||
stream.write_all(response.as_bytes()).unwrap();
|
||||
}</code></pre>
|
||||
<figcaption><a href="#listing-21-3">Listing 21-3</a>: Writing a tiny successful HTTP response to the stream</figcaption>
|
||||
</figure>
|
||||
<p>The first new line defines the <code>response</code> variable that holds the success
|
||||
message’s data. Then, we call <code>as_bytes</code> on our <code>response</code> to convert the
|
||||
string data to bytes. The <code>write_all</code> method on <code>stream</code> takes a <code>&[u8]</code> and
|
||||
sends those bytes directly down the connection. Because the <code>write_all</code>
|
||||
operation could fail, we use <code>unwrap</code> on any error result as before. Again, in
|
||||
a real application, you would add error handling here.</p>
|
||||
<p>With these changes, let’s run our code and make a request. We’re no longer
|
||||
printing any data to the terminal, so we won’t see any output other than the
|
||||
output from Cargo. When you load <em>127.0.0.1:7878</em> in a web browser, you should
|
||||
get a blank page instead of an error. You’ve just handcoded receiving an HTTP
|
||||
request and sending a response!</p>
|
||||
<h3 id="returning-real-html"><a class="header" href="#returning-real-html">Returning Real HTML</a></h3>
|
||||
<p>Let’s implement the functionality for returning more than a blank page. Create
|
||||
the new file <em>hello.html</em> in the root of your project directory, not in the
|
||||
<em>src</em> directory. You can input any HTML you want; Listing 21-4 shows one
|
||||
possibility.</p>
|
||||
<figure class="listing" id="listing-21-4">
|
||||
<span class="file-name">Filename: hello.html</span>
|
||||
<pre><code class="language-html"><!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Hello!</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello!</h1>
|
||||
<p>Hi from Rust</p>
|
||||
</body>
|
||||
</html>
|
||||
</code></pre>
|
||||
<figcaption><a href="#listing-21-4">Listing 21-4</a>: A sample HTML file to return in a response</figcaption>
|
||||
</figure>
|
||||
<p>This is a minimal HTML5 document with a heading and some text. To return this
|
||||
from the server when a request is received, we’ll modify <code>handle_connection</code> as
|
||||
shown in Listing 21-5 to read the HTML file, add it to the response as a body,
|
||||
and send it.</p>
|
||||
<figure class="listing" id="listing-21-5">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust no_run edition2024">use std::{
|
||||
fs,
|
||||
io::{BufReader, prelude::*},
|
||||
net::{TcpListener, TcpStream},
|
||||
};
|
||||
// --snip--
|
||||
|
||||
<span class="boring">fn main() {
|
||||
</span><span class="boring"> let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> for stream in listener.incoming() {
|
||||
</span><span class="boring"> let stream = stream.unwrap();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> handle_connection(stream);
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>fn handle_connection(mut stream: TcpStream) {
|
||||
let buf_reader = BufReader::new(&stream);
|
||||
let http_request: Vec<_> = buf_reader
|
||||
.lines()
|
||||
.map(|result| result.unwrap())
|
||||
.take_while(|line| !line.is_empty())
|
||||
.collect();
|
||||
|
||||
let status_line = "HTTP/1.1 200 OK";
|
||||
let contents = fs::read_to_string("hello.html").unwrap();
|
||||
let length = contents.len();
|
||||
|
||||
let response =
|
||||
format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
|
||||
|
||||
stream.write_all(response.as_bytes()).unwrap();
|
||||
}</code></pre>
|
||||
<figcaption><a href="#listing-21-5">Listing 21-5</a>: Sending the contents of <em>hello.html</em> as the body of the response</figcaption>
|
||||
</figure>
|
||||
<p>We’ve added <code>fs</code> to the <code>use</code> statement to bring the standard library’s
|
||||
filesystem module into scope. The code for reading the contents of a file to a
|
||||
string should look familiar; we used it when we read the contents of a file for
|
||||
our I/O project in Listing 12-4.</p>
|
||||
<p>Next, we use <code>format!</code> to add the file’s contents as the body of the success
|
||||
response. To ensure a valid HTTP response, we add the <code>Content-Length</code> header,
|
||||
which is set to the size of our response body—in this case, the size of
|
||||
<code>hello.html</code>.</p>
|
||||
<p>Run this code with <code>cargo run</code> and load <em>127.0.0.1:7878</em> in your browser; you
|
||||
should see your HTML rendered!</p>
|
||||
<p>Currently, we’re ignoring the request data in <code>http_request</code> and just sending
|
||||
back the contents of the HTML file unconditionally. That means if you try
|
||||
requesting <em>127.0.0.1:7878/something-else</em> in your browser, you’ll still get
|
||||
back this same HTML response. At the moment, our server is very limited and
|
||||
does not do what most web servers do. We want to customize our responses
|
||||
depending on the request and only send back the HTML file for a well-formed
|
||||
request to <em>/</em>.</p>
|
||||
<h3 id="validating-the-request-and-selectively-responding"><a class="header" href="#validating-the-request-and-selectively-responding">Validating the Request and Selectively Responding</a></h3>
|
||||
<p>Right now, our web server will return the HTML in the file no matter what the
|
||||
client requested. Let’s add functionality to check that the browser is
|
||||
requesting <em>/</em> before returning the HTML file and to return an error if the
|
||||
browser requests anything else. For this we need to modify <code>handle_connection</code>,
|
||||
as shown in Listing 21-6. This new code checks the content of the request
|
||||
received against what we know a request for <em>/</em> looks like and adds <code>if</code> and
|
||||
<code>else</code> blocks to treat requests differently.</p>
|
||||
<figure class="listing" id="listing-21-6">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust no_run edition2024"><span class="boring">use std::{
|
||||
</span><span class="boring"> fs,
|
||||
</span><span class="boring"> io::{BufReader, prelude::*},
|
||||
</span><span class="boring"> net::{TcpListener, TcpStream},
|
||||
</span><span class="boring">};
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> for stream in listener.incoming() {
|
||||
</span><span class="boring"> let stream = stream.unwrap();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> handle_connection(stream);
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span>// --snip--
|
||||
|
||||
fn handle_connection(mut stream: TcpStream) {
|
||||
let buf_reader = BufReader::new(&stream);
|
||||
let request_line = buf_reader.lines().next().unwrap().unwrap();
|
||||
|
||||
if request_line == "GET / HTTP/1.1" {
|
||||
let status_line = "HTTP/1.1 200 OK";
|
||||
let contents = fs::read_to_string("hello.html").unwrap();
|
||||
let length = contents.len();
|
||||
|
||||
let response = format!(
|
||||
"{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"
|
||||
);
|
||||
|
||||
stream.write_all(response.as_bytes()).unwrap();
|
||||
} else {
|
||||
// some other request
|
||||
}
|
||||
}</code></pre>
|
||||
<figcaption><a href="#listing-21-6">Listing 21-6</a>: Handling requests to <em>/</em> differently from other requests</figcaption>
|
||||
</figure>
|
||||
<p>We’re only going to be looking at the first line of the HTTP request, so rather
|
||||
than reading the entire request into a vector, we’re calling <code>next</code> to get the
|
||||
first item from the iterator. The first <code>unwrap</code> takes care of the <code>Option</code> and
|
||||
stops the program if the iterator has no items. The second <code>unwrap</code> handles the
|
||||
<code>Result</code> and has the same effect as the <code>unwrap</code> that was in the <code>map</code> added in
|
||||
Listing 21-2.</p>
|
||||
<p>Next, we check the <code>request_line</code> to see if it equals the request line of a GET
|
||||
request to the <em>/</em> path. If it does, the <code>if</code> block returns the contents of our
|
||||
HTML file.</p>
|
||||
<p>If the <code>request_line</code> does <em>not</em> equal the GET request to the <em>/</em> path, it
|
||||
means we’ve received some other request. We’ll add code to the <code>else</code> block in
|
||||
a moment to respond to all other requests.</p>
|
||||
<p>Run this code now and request <em>127.0.0.1:7878</em>; you should get the HTML in
|
||||
<em>hello.html</em>. If you make any other request, such as
|
||||
<em>127.0.0.1:7878/something-else</em>, you’ll get a connection error like those you
|
||||
saw when running the code in Listing 21-1 and Listing 21-2.</p>
|
||||
<p>Now let’s add the code in Listing 21-7 to the <code>else</code> block to return a response
|
||||
with the status code 404, which signals that the content for the request was
|
||||
not found. We’ll also return some HTML for a page to render in the browser
|
||||
indicating the response to the end user.</p>
|
||||
<figure class="listing" id="listing-21-7">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust no_run edition2024"><span class="boring">use std::{
|
||||
</span><span class="boring"> fs,
|
||||
</span><span class="boring"> io::{BufReader, prelude::*},
|
||||
</span><span class="boring"> net::{TcpListener, TcpStream},
|
||||
</span><span class="boring">};
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> for stream in listener.incoming() {
|
||||
</span><span class="boring"> let stream = stream.unwrap();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> handle_connection(stream);
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn handle_connection(mut stream: TcpStream) {
|
||||
</span><span class="boring"> let buf_reader = BufReader::new(&stream);
|
||||
</span><span class="boring"> let request_line = buf_reader.lines().next().unwrap().unwrap();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> if request_line == "GET / HTTP/1.1" {
|
||||
</span><span class="boring"> let status_line = "HTTP/1.1 200 OK";
|
||||
</span><span class="boring"> let contents = fs::read_to_string("hello.html").unwrap();
|
||||
</span><span class="boring"> let length = contents.len();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let response = format!(
|
||||
</span><span class="boring"> "{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"
|
||||
</span><span class="boring"> );
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> stream.write_all(response.as_bytes()).unwrap();
|
||||
</span> // --snip--
|
||||
} else {
|
||||
let status_line = "HTTP/1.1 404 NOT FOUND";
|
||||
let contents = fs::read_to_string("404.html").unwrap();
|
||||
let length = contents.len();
|
||||
|
||||
let response = format!(
|
||||
"{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"
|
||||
);
|
||||
|
||||
stream.write_all(response.as_bytes()).unwrap();
|
||||
}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-21-7">Listing 21-7</a>: Responding with status code 404 and an error page if anything other than <em>/</em> was requested</figcaption>
|
||||
</figure>
|
||||
<p>Here, our response has a status line with status code 404 and the reason phrase
|
||||
<code>NOT FOUND</code>. The body of the response will be the HTML in the file <em>404.html</em>.
|
||||
You’ll need to create a <em>404.html</em> file next to <em>hello.html</em> for the error
|
||||
page; again, feel free to use any HTML you want, or use the example HTML in
|
||||
Listing 21-8.</p>
|
||||
<figure class="listing" id="listing-21-8">
|
||||
<span class="file-name">Filename: 404.html</span>
|
||||
<pre><code class="language-html"><!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Hello!</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Oops!</h1>
|
||||
<p>Sorry, I don't know what you're asking for.</p>
|
||||
</body>
|
||||
</html>
|
||||
</code></pre>
|
||||
<figcaption><a href="#listing-21-8">Listing 21-8</a>: Sample content for the page to send back with any 404 response</figcaption>
|
||||
</figure>
|
||||
<p>With these changes, run your server again. Requesting <em>127.0.0.1:7878</em> should
|
||||
return the contents of <em>hello.html</em>, and any other request, like
|
||||
<em>127.0.0.1:7878/foo</em>, should return the error HTML from <em>404.html</em>.</p>
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<p><a id="a-touch-of-refactoring"></a></p>
|
||||
<h3 id="refactoring"><a class="header" href="#refactoring">Refactoring</a></h3>
|
||||
<p>At the moment, the <code>if</code> and <code>else</code> blocks have a lot of repetition: They’re
|
||||
both reading files and writing the contents of the files to the stream. The
|
||||
only differences are the status line and the filename. Let’s make the code more
|
||||
concise by pulling out those differences into separate <code>if</code> and <code>else</code> lines
|
||||
that will assign the values of the status line and the filename to variables;
|
||||
we can then use those variables unconditionally in the code to read the file
|
||||
and write the response. Listing 21-9 shows the resultant code after replacing
|
||||
the large <code>if</code> and <code>else</code> blocks.</p>
|
||||
<figure class="listing" id="listing-21-9">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust no_run edition2024"><span class="boring">use std::{
|
||||
</span><span class="boring"> fs,
|
||||
</span><span class="boring"> io::{BufReader, prelude::*},
|
||||
</span><span class="boring"> net::{TcpListener, TcpStream},
|
||||
</span><span class="boring">};
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> for stream in listener.incoming() {
|
||||
</span><span class="boring"> let stream = stream.unwrap();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> handle_connection(stream);
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span>// --snip--
|
||||
|
||||
fn handle_connection(mut stream: TcpStream) {
|
||||
// --snip--
|
||||
<span class="boring"> let buf_reader = BufReader::new(&stream);
|
||||
</span><span class="boring"> let request_line = buf_reader.lines().next().unwrap().unwrap();
|
||||
</span>
|
||||
let (status_line, filename) = if request_line == "GET / HTTP/1.1" {
|
||||
("HTTP/1.1 200 OK", "hello.html")
|
||||
} else {
|
||||
("HTTP/1.1 404 NOT FOUND", "404.html")
|
||||
};
|
||||
|
||||
let contents = fs::read_to_string(filename).unwrap();
|
||||
let length = contents.len();
|
||||
|
||||
let response =
|
||||
format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
|
||||
|
||||
stream.write_all(response.as_bytes()).unwrap();
|
||||
}</code></pre>
|
||||
<figcaption><a href="#listing-21-9">Listing 21-9</a>: Refactoring the <code>if</code> and <code>else</code> blocks to contain only the code that differs between the two cases</figcaption>
|
||||
</figure>
|
||||
<p>Now the <code>if</code> and <code>else</code> blocks only return the appropriate values for the
|
||||
status line and filename in a tuple; we then use destructuring to assign these
|
||||
two values to <code>status_line</code> and <code>filename</code> using a pattern in the <code>let</code>
|
||||
statement, as discussed in Chapter 19.</p>
|
||||
<p>The previously duplicated code is now outside the <code>if</code> and <code>else</code> blocks and
|
||||
uses the <code>status_line</code> and <code>filename</code> variables. This makes it easier to see
|
||||
the difference between the two cases, and it means we have only one place to
|
||||
update the code if we want to change how the file reading and response writing
|
||||
work. The behavior of the code in Listing 21-9 will be the same as that in
|
||||
Listing 21-7.</p>
|
||||
<p>Awesome! We now have a simple web server in approximately 40 lines of Rust code
|
||||
that responds to one request with a page of content and responds to all other
|
||||
requests with a 404 response.</p>
|
||||
<p>Currently, our server runs in a single thread, meaning it can only serve one
|
||||
request at a time. Let’s examine how that can be a problem by simulating some
|
||||
slow requests. Then, we’ll fix it so that our server can handle multiple
|
||||
requests at once.</p>
|
||||
</body>
|
||||
</html>
|
||||
1265
ch21/ch21-02-multithreaded.html
Normal file
1265
ch21/ch21-02-multithreaded.html
Normal file
File diff suppressed because it is too large
Load Diff
710
ch21/ch21-03-graceful-shutdown-and-cleanup.html
Normal file
710
ch21/ch21-03-graceful-shutdown-and-cleanup.html
Normal file
@@ -0,0 +1,710 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Graceful Shutdown and Cleanup</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2 id="graceful-shutdown-and-cleanup"><a class="header" href="#graceful-shutdown-and-cleanup">Graceful Shutdown and Cleanup</a></h2>
|
||||
<p>The code in Listing 21-20 is responding to requests asynchronously through the
|
||||
use of a thread pool, as we intended. We get some warnings about the <code>workers</code>,
|
||||
<code>id</code>, and <code>thread</code> fields that we’re not using in a direct way that reminds us
|
||||
we’re not cleaning up anything. When we use the less elegant
|
||||
<kbd>ctrl</kbd>-<kbd>C</kbd> method to halt the main thread, all other threads
|
||||
are stopped immediately as well, even if they’re in the middle of serving a
|
||||
request.</p>
|
||||
<p>Next, then, we’ll implement the <code>Drop</code> trait to call <code>join</code> on each of the
|
||||
threads in the pool so that they can finish the requests they’re working on
|
||||
before closing. Then, we’ll implement a way to tell the threads they should
|
||||
stop accepting new requests and shut down. To see this code in action, we’ll
|
||||
modify our server to accept only two requests before gracefully shutting down
|
||||
its thread pool.</p>
|
||||
<p>One thing to notice as we go: None of this affects the parts of the code that
|
||||
handle executing the closures, so everything here would be the same if we were
|
||||
using a thread pool for an async runtime.</p>
|
||||
<h3 id="implementing-the-drop-trait-on-threadpool"><a class="header" href="#implementing-the-drop-trait-on-threadpool">Implementing the <code>Drop</code> Trait on <code>ThreadPool</code></a></h3>
|
||||
<p>Let’s start with implementing <code>Drop</code> on our thread pool. When the pool is
|
||||
dropped, our threads should all join to make sure they finish their work.
|
||||
Listing 21-22 shows a first attempt at a <code>Drop</code> implementation; this code won’t
|
||||
quite work yet.</p>
|
||||
<figure class="listing" id="listing-21-22">
|
||||
<span class="file-name">Filename: src/lib.rs</span>
|
||||
<pre><code class="language-rust ignore does_not_compile"><span class="boring">use std::{
|
||||
</span><span class="boring"> sync::{Arc, Mutex, mpsc},
|
||||
</span><span class="boring"> thread,
|
||||
</span><span class="boring">};
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">pub struct ThreadPool {
|
||||
</span><span class="boring"> workers: Vec<Worker>,
|
||||
</span><span class="boring"> sender: mpsc::Sender<Job>,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">type Job = Box<dyn FnOnce() + Send + 'static>;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl ThreadPool {
|
||||
</span><span class="boring"> /// Create a new ThreadPool.
|
||||
</span><span class="boring"> ///
|
||||
</span><span class="boring"> /// The size is the number of threads in the pool.
|
||||
</span><span class="boring"> ///
|
||||
</span><span class="boring"> /// # Panics
|
||||
</span><span class="boring"> ///
|
||||
</span><span class="boring"> /// The `new` function will panic if the size is zero.
|
||||
</span><span class="boring"> pub fn new(size: usize) -> ThreadPool {
|
||||
</span><span class="boring"> assert!(size > 0);
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let (sender, receiver) = mpsc::channel();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let receiver = Arc::new(Mutex::new(receiver));
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let mut workers = Vec::with_capacity(size);
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> for id in 0..size {
|
||||
</span><span class="boring"> workers.push(Worker::new(id, Arc::clone(&receiver)));
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> ThreadPool { workers, sender }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> pub fn execute<F>(&self, f: F)
|
||||
</span><span class="boring"> where
|
||||
</span><span class="boring"> F: FnOnce() + Send + 'static,
|
||||
</span><span class="boring"> {
|
||||
</span><span class="boring"> let job = Box::new(f);
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> self.sender.send(job).unwrap();
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>impl Drop for ThreadPool {
|
||||
fn drop(&mut self) {
|
||||
for worker in &mut self.workers {
|
||||
println!("Shutting down worker {}", worker.id);
|
||||
|
||||
worker.thread.join().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
<span class="boring">
|
||||
</span><span class="boring">struct Worker {
|
||||
</span><span class="boring"> id: usize,
|
||||
</span><span class="boring"> thread: thread::JoinHandle<()>,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl Worker {
|
||||
</span><span class="boring"> fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
|
||||
</span><span class="boring"> let thread = thread::spawn(move || {
|
||||
</span><span class="boring"> loop {
|
||||
</span><span class="boring"> let job = receiver.lock().unwrap().recv().unwrap();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> println!("Worker {id} got a job; executing.");
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> job();
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> });
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> Worker { id, thread }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-21-22">Listing 21-22</a>: Joining each thread when the thread pool goes out of scope</figcaption>
|
||||
</figure>
|
||||
<p>First, we loop through each of the thread pool <code>workers</code>. We use <code>&mut</code> for this
|
||||
because <code>self</code> is a mutable reference, and we also need to be able to mutate
|
||||
<code>worker</code>. For each <code>worker</code>, we print a message saying that this particular
|
||||
<code>Worker</code> instance is shutting down, and then we call <code>join</code> on that <code>Worker</code>
|
||||
instance’s thread. If the call to <code>join</code> fails, we use <code>unwrap</code> to make Rust
|
||||
panic and go into an ungraceful shutdown.</p>
|
||||
<p>Here is the error we get when we compile this code:</p>
|
||||
<pre><code class="language-console">$ cargo check
|
||||
Checking hello v0.1.0 (file:///projects/hello)
|
||||
error[E0507]: cannot move out of `worker.thread` which is behind a mutable reference
|
||||
--> src/lib.rs:52:13
|
||||
|
|
||||
52 | worker.thread.join().unwrap();
|
||||
| ^^^^^^^^^^^^^ ------ `worker.thread` moved due to this method call
|
||||
| |
|
||||
| move occurs because `worker.thread` has type `JoinHandle<()>`, which does not implement the `Copy` trait
|
||||
|
|
||||
note: `JoinHandle::<T>::join` takes ownership of the receiver `self`, which moves `worker.thread`
|
||||
--> /rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/thread/mod.rs:1921:17
|
||||
|
||||
For more information about this error, try `rustc --explain E0507`.
|
||||
error: could not compile `hello` (lib) due to 1 previous error
|
||||
</code></pre>
|
||||
<p>The error tells us we can’t call <code>join</code> because we only have a mutable borrow
|
||||
of each <code>worker</code> and <code>join</code> takes ownership of its argument. To solve this
|
||||
issue, we need to move the thread out of the <code>Worker</code> instance that owns
|
||||
<code>thread</code> so that <code>join</code> can consume the thread. One way to do this is to take
|
||||
the same approach we took in Listing 18-15. If <code>Worker</code> held an
|
||||
<code>Option<thread::JoinHandle<()>></code>, we could call the <code>take</code> method on the
|
||||
<code>Option</code> to move the value out of the <code>Some</code> variant and leave a <code>None</code> variant
|
||||
in its place. In other words, a <code>Worker</code> that is running would have a <code>Some</code>
|
||||
variant in <code>thread</code>, and when we wanted to clean up a <code>Worker</code>, we’d replace
|
||||
<code>Some</code> with <code>None</code> so that the <code>Worker</code> wouldn’t have a thread to run.</p>
|
||||
<p>However, the <em>only</em> time this would come up would be when dropping the
|
||||
<code>Worker</code>. In exchange, we’d have to deal with an
|
||||
<code>Option<thread::JoinHandle<()>></code> anywhere we accessed <code>worker.thread</code>.
|
||||
Idiomatic Rust uses <code>Option</code> quite a bit, but when you find yourself wrapping
|
||||
something you know will always be present in an <code>Option</code> as a workaround like
|
||||
this, it’s a good idea to look for alternative approaches to make your code
|
||||
cleaner and less error-prone.</p>
|
||||
<p>In this case, a better alternative exists: the <code>Vec::drain</code> method. It accepts
|
||||
a range parameter to specify which items to remove from the vector and returns
|
||||
an iterator of those items. Passing the <code>..</code> range syntax will remove <em>every</em>
|
||||
value from the vector.</p>
|
||||
<p>So, we need to update the <code>ThreadPool</code> <code>drop</code> implementation like this:</p>
|
||||
<figure class="listing">
|
||||
<span class="file-name">Filename: src/lib.rs</span>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring">use std::{
|
||||
</span><span class="boring"> sync::{Arc, Mutex, mpsc},
|
||||
</span><span class="boring"> thread,
|
||||
</span><span class="boring">};
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">pub struct ThreadPool {
|
||||
</span><span class="boring"> workers: Vec<Worker>,
|
||||
</span><span class="boring"> sender: mpsc::Sender<Job>,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">type Job = Box<dyn FnOnce() + Send + 'static>;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl ThreadPool {
|
||||
</span><span class="boring"> /// Create a new ThreadPool.
|
||||
</span><span class="boring"> ///
|
||||
</span><span class="boring"> /// The size is the number of threads in the pool.
|
||||
</span><span class="boring"> ///
|
||||
</span><span class="boring"> /// # Panics
|
||||
</span><span class="boring"> ///
|
||||
</span><span class="boring"> /// The `new` function will panic if the size is zero.
|
||||
</span><span class="boring"> pub fn new(size: usize) -> ThreadPool {
|
||||
</span><span class="boring"> assert!(size > 0);
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let (sender, receiver) = mpsc::channel();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let receiver = Arc::new(Mutex::new(receiver));
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let mut workers = Vec::with_capacity(size);
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> for id in 0..size {
|
||||
</span><span class="boring"> workers.push(Worker::new(id, Arc::clone(&receiver)));
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> ThreadPool { workers, sender }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> pub fn execute<F>(&self, f: F)
|
||||
</span><span class="boring"> where
|
||||
</span><span class="boring"> F: FnOnce() + Send + 'static,
|
||||
</span><span class="boring"> {
|
||||
</span><span class="boring"> let job = Box::new(f);
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> self.sender.send(job).unwrap();
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>impl Drop for ThreadPool {
|
||||
fn drop(&mut self) {
|
||||
for worker in self.workers.drain(..) {
|
||||
println!("Shutting down worker {}", worker.id);
|
||||
|
||||
worker.thread.join().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
<span class="boring">
|
||||
</span><span class="boring">struct Worker {
|
||||
</span><span class="boring"> id: usize,
|
||||
</span><span class="boring"> thread: thread::JoinHandle<()>,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl Worker {
|
||||
</span><span class="boring"> fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
|
||||
</span><span class="boring"> let thread = thread::spawn(move || {
|
||||
</span><span class="boring"> loop {
|
||||
</span><span class="boring"> let job = receiver.lock().unwrap().recv().unwrap();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> println!("Worker {id} got a job; executing.");
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> job();
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> });
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> Worker { id, thread }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
</figure>
|
||||
<p>This resolves the compiler error and does not require any other changes to our
|
||||
code. Note that, because drop can be called when panicking, the unwrap
|
||||
could also panic and cause a double panic, which immediately crashes the
|
||||
program and ends any cleanup in progress. This is fine for an example program,
|
||||
but it isn’t recommended for production code.</p>
|
||||
<h3 id="signaling-to-the-threads-to-stop-listening-for-jobs"><a class="header" href="#signaling-to-the-threads-to-stop-listening-for-jobs">Signaling to the Threads to Stop Listening for Jobs</a></h3>
|
||||
<p>With all the changes we’ve made, our code compiles without any warnings.
|
||||
However, the bad news is that this code doesn’t function the way we want it to
|
||||
yet. The key is the logic in the closures run by the threads of the <code>Worker</code>
|
||||
instances: At the moment, we call <code>join</code>, but that won’t shut down the threads,
|
||||
because they <code>loop</code> forever looking for jobs. If we try to drop our
|
||||
<code>ThreadPool</code> with our current implementation of <code>drop</code>, the main thread will
|
||||
block forever, waiting for the first thread to finish.</p>
|
||||
<p>To fix this problem, we’ll need a change in the <code>ThreadPool</code> <code>drop</code>
|
||||
implementation and then a change in the <code>Worker</code> loop.</p>
|
||||
<p>First, we’ll change the <code>ThreadPool</code> <code>drop</code> implementation to explicitly drop
|
||||
the <code>sender</code> before waiting for the threads to finish. Listing 21-23 shows the
|
||||
changes to <code>ThreadPool</code> to explicitly drop <code>sender</code>. Unlike with the thread,
|
||||
here we <em>do</em> need to use an <code>Option</code> to be able to move <code>sender</code> out of
|
||||
<code>ThreadPool</code> with <code>Option::take</code>.</p>
|
||||
<figure class="listing" id="listing-21-23">
|
||||
<span class="file-name">Filename: src/lib.rs</span>
|
||||
<pre><code class="language-rust noplayground not_desired_behavior"><span class="boring">use std::{
|
||||
</span><span class="boring"> sync::{Arc, Mutex, mpsc},
|
||||
</span><span class="boring"> thread,
|
||||
</span><span class="boring">};
|
||||
</span><span class="boring">
|
||||
</span>pub struct ThreadPool {
|
||||
workers: Vec<Worker>,
|
||||
sender: Option<mpsc::Sender<Job>>,
|
||||
}
|
||||
// --snip--
|
||||
<span class="boring">
|
||||
</span><span class="boring">type Job = Box<dyn FnOnce() + Send + 'static>;
|
||||
</span><span class="boring">
|
||||
</span>impl ThreadPool {
|
||||
<span class="boring"> /// Create a new ThreadPool.
|
||||
</span><span class="boring"> ///
|
||||
</span><span class="boring"> /// The size is the number of threads in the pool.
|
||||
</span><span class="boring"> ///
|
||||
</span><span class="boring"> /// # Panics
|
||||
</span><span class="boring"> ///
|
||||
</span><span class="boring"> /// The `new` function will panic if the size is zero.
|
||||
</span> pub fn new(size: usize) -> ThreadPool {
|
||||
// --snip--
|
||||
|
||||
<span class="boring"> assert!(size > 0);
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let (sender, receiver) = mpsc::channel();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let receiver = Arc::new(Mutex::new(receiver));
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let mut workers = Vec::with_capacity(size);
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> for id in 0..size {
|
||||
</span><span class="boring"> workers.push(Worker::new(id, Arc::clone(&receiver)));
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span> 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 self.workers.drain(..) {
|
||||
println!("Shutting down worker {}", worker.id);
|
||||
|
||||
worker.thread.join().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
<span class="boring">
|
||||
</span><span class="boring">struct Worker {
|
||||
</span><span class="boring"> id: usize,
|
||||
</span><span class="boring"> thread: thread::JoinHandle<()>,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl Worker {
|
||||
</span><span class="boring"> fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
|
||||
</span><span class="boring"> let thread = thread::spawn(move || {
|
||||
</span><span class="boring"> loop {
|
||||
</span><span class="boring"> let job = receiver.lock().unwrap().recv().unwrap();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> println!("Worker {id} got a job; executing.");
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> job();
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> });
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> Worker { id, thread }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-21-23">Listing 21-23</a>: Explicitly dropping <code>sender</code> before joining the <code>Worker</code> threads</figcaption>
|
||||
</figure>
|
||||
<p>Dropping <code>sender</code> closes the channel, which indicates no more messages will be
|
||||
sent. When that happens, all the calls to <code>recv</code> that the <code>Worker</code> instances do
|
||||
in the infinite loop will return an error. In Listing 21-24, we change the
|
||||
<code>Worker</code> loop to gracefully exit the loop in that case, which means the threads
|
||||
will finish when the <code>ThreadPool</code> <code>drop</code> implementation calls <code>join</code> on them.</p>
|
||||
<figure class="listing" id="listing-21-24">
|
||||
<span class="file-name">Filename: src/lib.rs</span>
|
||||
<pre><code class="language-rust noplayground"><span class="boring">use std::{
|
||||
</span><span class="boring"> sync::{Arc, Mutex, mpsc},
|
||||
</span><span class="boring"> thread,
|
||||
</span><span class="boring">};
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">pub struct ThreadPool {
|
||||
</span><span class="boring"> workers: Vec<Worker>,
|
||||
</span><span class="boring"> sender: Option<mpsc::Sender<Job>>,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">type Job = Box<dyn FnOnce() + Send + 'static>;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl ThreadPool {
|
||||
</span><span class="boring"> /// Create a new ThreadPool.
|
||||
</span><span class="boring"> ///
|
||||
</span><span class="boring"> /// The size is the number of threads in the pool.
|
||||
</span><span class="boring"> ///
|
||||
</span><span class="boring"> /// # Panics
|
||||
</span><span class="boring"> ///
|
||||
</span><span class="boring"> /// The `new` function will panic if the size is zero.
|
||||
</span><span class="boring"> pub fn new(size: usize) -> ThreadPool {
|
||||
</span><span class="boring"> assert!(size > 0);
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let (sender, receiver) = mpsc::channel();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let receiver = Arc::new(Mutex::new(receiver));
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let mut workers = Vec::with_capacity(size);
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> for id in 0..size {
|
||||
</span><span class="boring"> workers.push(Worker::new(id, Arc::clone(&receiver)));
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> ThreadPool {
|
||||
</span><span class="boring"> workers,
|
||||
</span><span class="boring"> sender: Some(sender),
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> pub fn execute<F>(&self, f: F)
|
||||
</span><span class="boring"> where
|
||||
</span><span class="boring"> F: FnOnce() + Send + 'static,
|
||||
</span><span class="boring"> {
|
||||
</span><span class="boring"> let job = Box::new(f);
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> self.sender.as_ref().unwrap().send(job).unwrap();
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl Drop for ThreadPool {
|
||||
</span><span class="boring"> fn drop(&mut self) {
|
||||
</span><span class="boring"> drop(self.sender.take());
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> for worker in self.workers.drain(..) {
|
||||
</span><span class="boring"> println!("Shutting down worker {}", worker.id);
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> worker.thread.join().unwrap();
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">struct Worker {
|
||||
</span><span class="boring"> id: usize,
|
||||
</span><span class="boring"> thread: thread::JoinHandle<()>,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>impl Worker {
|
||||
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
|
||||
let thread = thread::spawn(move || {
|
||||
loop {
|
||||
let message = receiver.lock().unwrap().recv();
|
||||
|
||||
match message {
|
||||
Ok(job) => {
|
||||
println!("Worker {id} got a job; executing.");
|
||||
|
||||
job();
|
||||
}
|
||||
Err(_) => {
|
||||
println!("Worker {id} disconnected; shutting down.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Worker { id, thread }
|
||||
}
|
||||
}</code></pre>
|
||||
<figcaption><a href="#listing-21-24">Listing 21-24</a>: Explicitly breaking out of the loop when <code>recv</code> returns an error</figcaption>
|
||||
</figure>
|
||||
<p>To see this code in action, let’s modify <code>main</code> to accept only two requests
|
||||
before gracefully shutting down the server, as shown in Listing 21-25.</p>
|
||||
<figure class="listing" id="listing-21-25">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre><code class="language-rust ignore"><span class="boring">use hello::ThreadPool;
|
||||
</span><span class="boring">use std::{
|
||||
</span><span class="boring"> fs,
|
||||
</span><span class="boring"> io::{BufReader, prelude::*},
|
||||
</span><span class="boring"> net::{TcpListener, TcpStream},
|
||||
</span><span class="boring"> thread,
|
||||
</span><span class="boring"> time::Duration,
|
||||
</span><span class="boring">};
|
||||
</span><span class="boring">
|
||||
</span>fn main() {
|
||||
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
|
||||
let pool = ThreadPool::new(4);
|
||||
|
||||
for stream in listener.incoming().take(2) {
|
||||
let stream = stream.unwrap();
|
||||
|
||||
pool.execute(|| {
|
||||
handle_connection(stream);
|
||||
});
|
||||
}
|
||||
|
||||
println!("Shutting down.");
|
||||
}
|
||||
<span class="boring">
|
||||
</span><span class="boring">fn handle_connection(mut stream: TcpStream) {
|
||||
</span><span class="boring"> let buf_reader = BufReader::new(&stream);
|
||||
</span><span class="boring"> let request_line = buf_reader.lines().next().unwrap().unwrap();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let (status_line, filename) = match &request_line[..] {
|
||||
</span><span class="boring"> "GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
|
||||
</span><span class="boring"> "GET /sleep HTTP/1.1" => {
|
||||
</span><span class="boring"> thread::sleep(Duration::from_secs(5));
|
||||
</span><span class="boring"> ("HTTP/1.1 200 OK", "hello.html")
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> _ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
|
||||
</span><span class="boring"> };
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let contents = fs::read_to_string(filename).unwrap();
|
||||
</span><span class="boring"> let length = contents.len();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let response =
|
||||
</span><span class="boring"> format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> stream.write_all(response.as_bytes()).unwrap();
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-21-25">Listing 21-25</a>: Shutting down the server after serving two requests by exiting the loop</figcaption>
|
||||
</figure>
|
||||
<p>You wouldn’t want a real-world web server to shut down after serving only two
|
||||
requests. This code just demonstrates that the graceful shutdown and cleanup is
|
||||
in working order.</p>
|
||||
<p>The <code>take</code> method is defined in the <code>Iterator</code> trait and limits the iteration
|
||||
to the first two items at most. The <code>ThreadPool</code> will go out of scope at the
|
||||
end of <code>main</code>, and the <code>drop</code> implementation will run.</p>
|
||||
<p>Start the server with <code>cargo run</code> and make three requests. The third request
|
||||
should error, and in your terminal, you should see output similar to this:</p>
|
||||
<!-- manual-regeneration
|
||||
cd listings/ch21-web-server/listing-21-25
|
||||
cargo run
|
||||
curl http://127.0.0.1:7878
|
||||
curl http://127.0.0.1:7878
|
||||
curl http://127.0.0.1:7878
|
||||
third request will error because server will have shut down
|
||||
copy output below
|
||||
Can't automate because the output depends on making requests
|
||||
-->
|
||||
<pre><code class="language-console">$ cargo run
|
||||
Compiling hello v0.1.0 (file:///projects/hello)
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.41s
|
||||
Running `target/debug/hello`
|
||||
Worker 0 got a job; executing.
|
||||
Shutting down.
|
||||
Shutting down worker 0
|
||||
Worker 3 got a job; executing.
|
||||
Worker 1 disconnected; shutting down.
|
||||
Worker 2 disconnected; shutting down.
|
||||
Worker 3 disconnected; shutting down.
|
||||
Worker 0 disconnected; shutting down.
|
||||
Shutting down worker 1
|
||||
Shutting down worker 2
|
||||
Shutting down worker 3
|
||||
</code></pre>
|
||||
<p>You might see a different ordering of <code>Worker</code> IDs and messages printed. We can
|
||||
see how this code works from the messages: <code>Worker</code> instances 0 and 3 got the
|
||||
first two requests. The server stopped accepting connections after the second
|
||||
connection, and the <code>Drop</code> implementation on <code>ThreadPool</code> starts executing
|
||||
before <code>Worker 3</code> even starts its job. Dropping the <code>sender</code> disconnects all the
|
||||
<code>Worker</code> instances and tells them to shut down. The <code>Worker</code> instances each
|
||||
print a message when they disconnect, and then the thread pool calls <code>join</code> to
|
||||
wait for each <code>Worker</code> thread to finish.</p>
|
||||
<p>Notice one interesting aspect of this particular execution: The <code>ThreadPool</code>
|
||||
dropped the <code>sender</code>, and before any <code>Worker</code> received an error, we tried to
|
||||
join <code>Worker 0</code>. <code>Worker 0</code> had not yet gotten an error from <code>recv</code>, so the main
|
||||
thread blocked, waiting for <code>Worker 0</code> to finish. In the meantime, <code>Worker 3</code>
|
||||
received a job and then all threads received an error. When <code>Worker 0</code> finished,
|
||||
the main thread waited for the rest of the <code>Worker</code> instances to finish. At that
|
||||
point, they had all exited their loops and stopped.</p>
|
||||
<p>Congrats! We’ve now completed our project; we have a basic web server that uses
|
||||
a thread pool to respond asynchronously. We’re able to perform a graceful
|
||||
shutdown of the server, which cleans up all the threads in the pool.</p>
|
||||
<p>Here’s the full code for reference:</p>
|
||||
<figure class="listing">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre><code class="language-rust ignore">use hello::ThreadPool;
|
||||
use std::{
|
||||
fs,
|
||||
io::{BufReader, prelude::*},
|
||||
net::{TcpListener, TcpStream},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
|
||||
let pool = ThreadPool::new(4);
|
||||
|
||||
for stream in listener.incoming().take(2) {
|
||||
let stream = stream.unwrap();
|
||||
|
||||
pool.execute(|| {
|
||||
handle_connection(stream);
|
||||
});
|
||||
}
|
||||
|
||||
println!("Shutting down.");
|
||||
}
|
||||
|
||||
fn handle_connection(mut stream: TcpStream) {
|
||||
let buf_reader = BufReader::new(&stream);
|
||||
let request_line = buf_reader.lines().next().unwrap().unwrap();
|
||||
|
||||
let (status_line, filename) = match &request_line[..] {
|
||||
"GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
|
||||
"GET /sleep HTTP/1.1" => {
|
||||
thread::sleep(Duration::from_secs(5));
|
||||
("HTTP/1.1 200 OK", "hello.html")
|
||||
}
|
||||
_ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
|
||||
};
|
||||
|
||||
let contents = fs::read_to_string(filename).unwrap();
|
||||
let length = contents.len();
|
||||
|
||||
let response =
|
||||
format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
|
||||
|
||||
stream.write_all(response.as_bytes()).unwrap();
|
||||
}</code></pre>
|
||||
</figure>
|
||||
<figure class="listing">
|
||||
<span class="file-name">Filename: src/lib.rs</span>
|
||||
<pre><code class="language-rust noplayground">use std::{
|
||||
sync::{Arc, Mutex, mpsc},
|
||||
thread,
|
||||
};
|
||||
|
||||
pub struct ThreadPool {
|
||||
workers: Vec<Worker>,
|
||||
sender: Option<mpsc::Sender<Job>>,
|
||||
}
|
||||
|
||||
type Job = Box<dyn FnOnce() + Send + 'static>;
|
||||
|
||||
impl ThreadPool {
|
||||
/// Create a new ThreadPool.
|
||||
///
|
||||
/// The size is the number of threads in the pool.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// The `new` function will panic if the size is zero.
|
||||
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);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Worker {
|
||||
id: usize,
|
||||
thread: Option<thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Worker {
|
||||
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
|
||||
let thread = thread::spawn(move || {
|
||||
loop {
|
||||
let message = receiver.lock().unwrap().recv();
|
||||
|
||||
match message {
|
||||
Ok(job) => {
|
||||
println!("Worker {id} got a job; executing.");
|
||||
|
||||
job();
|
||||
}
|
||||
Err(_) => {
|
||||
println!("Worker {id} disconnected; shutting down.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Worker {
|
||||
id,
|
||||
thread: Some(thread),
|
||||
}
|
||||
}
|
||||
}</code></pre>
|
||||
</figure>
|
||||
<p>We could do more here! If you want to continue enhancing this project, here are
|
||||
some ideas:</p>
|
||||
<ul>
|
||||
<li>Add more documentation to <code>ThreadPool</code> and its public methods.</li>
|
||||
<li>Add tests of the library’s functionality.</li>
|
||||
<li>Change calls to <code>unwrap</code> to more robust error handling.</li>
|
||||
<li>Use <code>ThreadPool</code> to perform some task other than serving web requests.</li>
|
||||
<li>Find a thread pool crate on <a href="https://crates.io/">crates.io</a> and implement a
|
||||
similar web server using the crate instead. Then, compare its API and
|
||||
robustness to the thread pool we implemented.</li>
|
||||
</ul>
|
||||
<h2 id="summary"><a class="header" href="#summary">Summary</a></h2>
|
||||
<p>Well done! You’ve made it to the end of the book! We want to thank you for
|
||||
joining us on this tour of Rust. You’re now ready to implement your own Rust
|
||||
projects and help with other people’s projects. Keep in mind that there is a
|
||||
welcoming community of other Rustaceans who would love to help you with any
|
||||
challenges you encounter on your Rust journey.</p>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user