diff --git a/projects/hello/404.html b/projects/hello/404.html
deleted file mode 100644
index 49c36b1..0000000
--- a/projects/hello/404.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
- 你好!
-
-
- 糟糕!
- 抱歉,我不明白你要什么。
-
-
diff --git a/projects/hello/Cargo.toml b/projects/hello/Cargo.toml
index fb1ec2c..f6f3649 100644
--- a/projects/hello/Cargo.toml
+++ b/projects/hello/Cargo.toml
@@ -1,8 +1,6 @@
[package]
name = "hello"
version = "0.1.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+edition = "2024"
[dependencies]
diff --git a/projects/hello/hello.html b/projects/hello/hello.html
deleted file mode 100644
index b3d914e..0000000
--- a/projects/hello/hello.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
- 你好!
-
-
- 你好!
- 来自 Rust 的问好
-
-
diff --git a/projects/hello/src/lib.rs b/projects/hello/src/lib.rs
deleted file mode 100644
index 78e7e53..0000000
--- a/projects/hello/src/lib.rs
+++ /dev/null
@@ -1,92 +0,0 @@
-use std::{
- sync::{mpsc, Arc, Mutex},
- thread,
-};
-
-pub struct ThreadPool {
- workers: Vec,
- sender: Option>,
-}
-
-type Job = Box;
-
-impl ThreadPool {
- /// 创建出一个新的 ThreadPool。
- ///
- /// 其中的 size 为线程池中线程的数目。
- ///
- /// # 终止运行
- ///
- /// 这个 `new` 函数将在 size 为零时终止运行。
- 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(&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! ("关闭 worker {}", worker.id);
-
- if let Some(thread) = worker.thread.take() {
- thread.join().unwrap();
- }
- }
- }
-}
-
-struct Worker {
- id: usize,
- thread: Option>,
-}
-
-impl Worker {
- fn new(id: usize, receiver: Arc>>) -> Worker {
- let thread = thread::spawn(move || loop {
- let message = receiver.lock().unwrap().recv();
-
- match message {
- Ok(job) => {
- println! ("Worker {id} 获取到一项作业;执行中。");
-
- job();
- }
- Err(_) => {
- println! ("Worker {id} 已断开链接;关闭中。");
- break;
- }
- }
- });
-
- Worker {
- id,
- thread: Some(thread),
- }
- }
-}
diff --git a/projects/hello/src/main.rs b/projects/hello/src/main.rs
index b1fcbe2..fe31142 100644
--- a/projects/hello/src/main.rs
+++ b/projects/hello/src/main.rs
@@ -1,46 +1,25 @@
-use hello::ThreadPool;
-
use std::{
- fs,
- thread,
- io::{prelude::*, BufReader},
+ io::{BufReader, prelude::*},
net::{TcpListener, TcpStream},
- 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) {
+ for stream in listener.incoming() {
let stream = stream.unwrap();
- pool.execute(|| {
- handle_conn(stream);
- });
+ handle_connection(stream);
}
-
- println! ("关闭中。");
}
-fn handle_conn(mut stream: TcpStream) {
- let buf_reader = BufReader::new(&mut stream);
- let req_line = buf_reader.lines().next().unwrap().unwrap();
+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, filename) = match &req_line[..] {
- "GET / HTTP/1.1" => ( "HTTP/1.1 200 OK", "hello.html"),
- "GET /sleep HTTP/1.1" => {
- thread::sleep(Duration::from_secs(10));
- ("HTTP/1.1 200 0K", "hello.html")
- }
- _ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
- };
-
- let contents = fs::read_to_string(filename).unwrap();
- let length = contents.len();
-
- let resp =
- format! ("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
-
- stream.write_all(resp.as_bytes()).unwrap();
+ println!("Request: {http_request:#?}");
}
diff --git a/src/Ch21_Final_Project_Building_a_Multithreaded_Web_Server.md b/src/Ch21_Final_Project_Building_a_Multithreaded_Web_Server.md
index d1f0f29..bc33f8f 100644
--- a/src/Ch21_Final_Project_Building_a_Multithreaded_Web_Server.md
+++ b/src/Ch21_Final_Project_Building_a_Multithreaded_Web_Server.md
@@ -13,10 +13,10 @@
4. 创建正确的 HTTP 响应;
5. 通过线程池提升服务器的吞吐量。
-
+

-**图 20-1**:我们最后一个合作项目
+**图 21-1**:我们最后一个合作项目
在开始之前,我们需要说明两点。我们将使用的方法并非用 Rust 构建 web 服务器的最佳方式。社区成员已在 [crates.io](https://crates.io/) 上发布许多可用于生产环境的代码箱,他们提供了比我们构建的更完善的 web 服务器和线程池实现。然而,这一章中我们的目的是要帮助咱们学习,而非走捷径。正因为 Rust 是一门系统编程语言,所以我们可以自有选择想要的抽象级别,并能够深入到比其他语言所能达到或实际可行的更底层。
diff --git a/src/final_project/single-threaded.md b/src/final_project/single-threaded.md
index 4fcac25..dea578b 100644
--- a/src/final_project/single-threaded.md
+++ b/src/final_project/single-threaded.md
@@ -1,28 +1,27 @@
-# 构建一个单线程的 Web 服务器
+# 构建单线程的 Web 服务器
-咱们将通过让一个单线程的 web 服务器工作起来而开始。在咱们开始前,先来看看在构建 web 服务器中涉及到的一些协议的快速概览。这些协议的细节,超出了本书范围,而简要概述,就将给到咱们所需的信息。
+我们将从让单线程的 web 服务器运行开始。在开始之前,我们来先快速概述一下构建 web 服务器所涉及的协议。这些协议的细节超出了本书的范围,但简要概述就将给予咱们所需的信息。
-Web 服务器中涉及的两种主要谢谢,分别是 *超文本传输协议,Hypertext Transfer Protocol, HTTP* 与 *传输控制协议,Transmission Control Protocol, TCP*。两种协议都是 *请求-响应,request-response* 的协议,表示 *客户端,client* 发起请求,而 *服务器,server* 监听到请求并提供给客户端一个响应。这些请求和响应的内容是由两种协议定义的。
+Web 服务器涉及的两个主要协议,分别是 *超文本传输协议 HTTP* 和 *传输控制协议 TCP*。这两个协议都属于 *请求-响应* 协议,这意味着 *客户端* 发起请求,*服务器* 监听请求并向客户端提供响应。这些请求和响应的内容由协议定义。
-TCP 是种描述了信息如何从一台服务器到达另一服务器,但并未指明信息为何的低级别。HTTP 则是经由定义请求与响应的内容,而于 TCP 之上构建的。技术上要在其他协议上使用 HTTP 是可行的,但在绝大多数情况下,HTTP 都在 TCP 上发送他的数据。咱们将在 TCP 的原始字节,与 HTTP 请求和响应下,进行工作。
+TCP 属于底层协议,描述了信息如何从一台服务器传输到另一服务器的具体细节,但并未规定这些信息的内容。HTTP 通过定义请求和响应的内容,而构建于 TCP 之上。从技术上讲,于其他协议一起使用 HTTP 是可行的,但在绝大多数情况下,HTTP 都时通过 TCP 发送数据。我们将使用 TCP 和 HTTP 请求和响应的原始字节。
## 监听 TCP 连接
-**Listen to the TCP Connection**
-
-
-咱们的 web 服务器需要监听某个 TCP 连接,因此那便是咱们将要做的第一部分工作。标准库提供了一个 `std::net` 模组,允许咱们完成这一点。咱们来以寻常方式构造一个新的项目:
+我们的 web 服务器需要监听 TCP 连接,因此这是我们将要处理的第一部分。标准库提供了 `std::net` 模组,让我们可以做到这一点。我们来以惯常方式构造一个新项目:
```console
-$ cargo new hello --vcs none
- Created binary (application) `hello` package
+$ cargo new hello
+ Creating binary (application) `hello` package
+note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
$ cd hello
```
-现在请输入下面清单 20-1 中 `src/main.rs` 里的代码来开始。这段代码会在本地地址 `127.0.0.1:7878` 处监听传入的 TCP 流。当他获取到一个传入流时,他就会打印 `连接已建立!`。
+现在,请在 `src/main.rs` 中输入下面清单 21-1 中的代码来开始。这段代码将在本地地址 `127.0.0.1:7878` 上监听传入的 TCP 流。当他获取到传入流时,他将打印 `连接已建立!`。
-文件名:`src/main.rs`
+
+文件名:`projects/hello/src/main.rs`
```rust
use std::net::TcpListener;
@@ -38,50 +37,58 @@ fn main() {
}
```
-*清单 20-1:监听传入流并在咱们接收到某个流时打印一条消息*
+**清单 21-1**:监听传入流,并在接收到流时打印消息
-运用 `TcpListener`,咱们就可以在地址 `127.0.0.1:7878` 处监听 TCP 连接。在这个地址中,冒号之前的部分,是个表示咱们的计算机的 IP 地址(在所有计算机上这都是同样的,而并不特别表示本书作者的计算机),同时 `7878` 为端口。咱们之所以选择了这个端口,有两个原因:通常不是在这个端口上接收 HTTP,因此咱们的服务器,大概率不会与咱们可能在咱们的机器上运行的任何别的 web 服务器冲突,而 `7878` 则是电话机上输入的 *rust*。
+使用 `TcpListener`,我们可以监听地址 `127.0.0.1:7878` 上的 TCP 连接。在地址中,冒号之前的部分是个 IP 地之,代表咱们的计算机(这在每台计算机上都是相同的,并不特指本书作者的计算机),而 `7878` 是端口。我们选择这个端口有两个原因:HTTP 在这个端口上通常是不接受的,因此我们的服务器不太可能于咱们机器上运行的任何其他 web 服务器冲突,并且 `7878` 在电话上输入时听起来像 *rust*。
-这个场景中的 `bind` 函数,会像将返回一个新 `TcpListener` 实例的 `new` 函数一样工作。该函数之所以叫做 `bind`,是因为在网络通信中,连接到要监听的端口,被称为 “绑定到端口”。
+在这一情形下的 `bind` 函数的作用与 `new` 函数相似,将返回一个新的 `TcpListener` 实例。该函数称为 `bind`,因为在网络通信中,连接到某个要监听的端口称为 “绑定到端口”。
-`bind` 函数返回的是个 `Result`,表明有可能绑定失败。比如,连接到端口 `80` 需要管理员权限(非管理员只可以监听高于 `1023` 的那些端口,译注:在 *nix 平台上有此限制,但在 Win 平台上没有),因此若咱们在非管理员下尝试连接到端口 `80`,端口绑定就不会工作。在比如咱们运行了这个程序的两个实例,而因此有两个程序在监听同一端口时,端口绑定也不会工作。由于咱们仅是处于学习目的,而编写的一个基本服务器,因此咱们就不会纠结于处理这些类别的错误;相反,咱们使用 `unwrap` 来在错误发生时停止这个程序。
+`bind` 函数返回 `Result`,这表明绑定可能失败。例如,当我们运行了程序的两个实例,而因此让两个程序监听同一个端口时;由于我们只是出于学习目的而编写一个基础服务器,因此无需担心处理此类错误;相反,我们使用 `unwrap` 来在错误发生时停止程序。
-`TcpListener` 上的 `incoming` 方法,会返回一个给到咱们流(更具体的,是一些类型 `TcpStream` 的流)序列的迭代器,an iterator that gives us a sequence of streams。单一的 *流,stream* 表示了客户端与服务器之间的一个打开的连接,an open connection。而一个 *连接,connection* 则是客户端连接到服务器过程中,完整的请求与响应的叫法,服务器会生成一个响应,且服务器会关闭这个连接。就这样,咱们将从那个 `TcpStream` 读取,来看看客户端发送了什么,并于随后把咱们的响应写到这个流,以将数据发送回客户端。总的来说,这个 `for` 循环将依次处理每个连接,并为咱们产生一系列要处理的流。
+`TcpListener` 上的 `incoming` 方法返回一个迭代器,给予我们一个流的序列(更具体地说,是 `TcpStream` 类型的流)。单个 *流* 代表客户端与服务器之间的开放连接。所谓 *连接,conneciton*,是指完整的请求和响应过程,其中客户端连接到服务器,服务器生成响应,然后服务器关闭连接。因此,我们将从 `TcpStream` 中读取以查看客户端发送的内容,然后将响应写入流以将数据发送回客户端。总的来说,这个 `for` 循环将依次处理每个连接,并生成一系列供我们处理的流。
-至于现在,咱们对流的处理,是由在流有任何错误时,调用 `unwrap` 来终止咱们的程序所构成;若没有任何错误,那么这个程序就会打印一条消息。在下一代码清单中,咱们将为流成功的情形,添加更多功能。在客户端连接到服务器时,咱们可能会从那个 `incoming` 方法收到错误的原因,便是咱们没有真正在一些连接上迭代。相反,咱们是在一些 *连接尝试,connection attempts* 上迭代。连接可能因为数种原因而不成功,许多的这些原因都是特定于操作系统的。比如,许多操作系统都有他们所支持的并发开启连接数限制,a limit to the number of simultaneous open connecitons;超出那个数目的新建连接尝试就会产生错误,除非一些开启的连接关闭。
+目前,我们对流的处理包括
-咱们来尝试运行这段代码!在终端里运行 `cargo run` 并随后在 web 浏览器中加载 `127.0.0.1:7878`。由于服务器没有正确发回任何数据,因此浏览器应给出像是 `Connection reset,` 的错误消息。但当咱们看着终端时,应看到在浏览器连接到服务器时,有数条打印处的消息!
+- 当流出现任何错误时,调用 `unwrap` 来终止程序;
+- 当没有任何错误时,程序则打印一条消息。
-> 注:可使用 `$curl 127.0.0.1:7878` 命令进行调试,且使用 `curl` 也是网络编程调试中常用的方法。
+我们将在下一代码清单中为流成功的情形添加更多功能。当客户端连接到服务器时,我们可能会从 `incoming` 方法收到错误的原因在于,我们实际上并不是在遍历连接。而是在遍历 *连接尝试*。连接可能因多种原因而失败,其中许多原因都是特定于操作系统的。例如,许多操作系统对可支持的同时打开连接的数量有限制;超过该数量的新连接尝试将引发错误,知道部分已打开的连接被关闭为止。
+
+我们来尝试运行这段代码!在终端中执行 `cargo run`,然后在 web 浏览器中加载 `127.0.0.1:7878`。由于服务器当前尚未返回任何数据,浏览器应该显示 `Connection reset,` 的错误消息。但当咱们查看终端时,应该看到浏览器连接到服务器时打印的几条消息!
+
+> **译注**:可使用 `curl 127.0.0.1:7878` 命令进行调试,且使用 `curl` 也是网络编程调试中常用的方法。
```console
+ Compiling hello v0.1.0 (/home/hector/rust-lang-zh_CN/projects/hello)
+ Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.26s
Running `target/debug/hello`
-连接已建立!
+
连接已建立!
连接已建立!
连接已建立!
```
-有的时候,咱们会看到一次浏览器请求下打印出的多条消息;原因可能是浏览器在构造页面请求时,也会构造其他资源的请求,像是出现在浏览器 tab 分页中的 `favicon.ico` 图标。
+有时咱们会看到针对一次浏览器请求打印多条消息;原因可能是浏览器在请求页面时,同时也请求了其他资源,例如显示在浏览器标签页中的 `favicon.ico` 图标。
-也可由可能是由于这个服务器没有响应任何数据,浏览器因此会尝试多次连接到这个服务器。在 `stream` 超出作用域,而在那个循环结束出被丢弃时,连接就会作为 `drop` 实现的一部分而被关闭。由于故障可能是临时的,因此浏览器有时会以重试处理关闭的连接。重要的是,咱们已然成功得到了到 TCP 连接的句柄,a handle to a TCP connection!
+也可能是因为服务器没有响应任何数据,导致浏览器尝试多次连接到服务器。当 `stream` 超出作用域并在循环结束时被启用时,连接将作为 `drop` 实现的一部分而被关闭。浏览器有时会通过重试来处理关闭的连接,因为问题可能是暂时的。
-请记得在咱们完成运行代码的特定版本时,要通过按下 `Ctrl-c` 来停止这个程序。以后在完成了各套代码修改后,要通过运行 `cargo run` 命令重启这个程序,来确保咱们是在运行最新的代码。
+浏览器有时也会在不发送任何请求的情况下,打开与服务器的多个连接,以便稍后 *真正* 发送请求时,这些请求可以更快地发生。当这种情况发生时,我们的服务器将发现到每个连接,无论连接上是否有请求。例如,许多基于 Chrome 的浏览器版本都会这样做;咱们可以通过使用隐私浏览模式,或使用其他浏览器来禁用这一优化。
+
+重要的是我们已经成功获得了 TCP 连接的句柄!
+
+请记住在运行完特定版本的代码后按下 `Ctrl-c` 来停止程序。然后,在完成每套代码修改后,要通过运行 `cargo run` 命令重启程序,以确保运行的是最新版本的代码。
## 读取请求
-**Reading the Request**
-
-
-咱们来实现读取来自浏览器请求的功能!为将首选获取到连接,及随后对连接采取一些措施这两个关注点分离,咱们将开启一个用于处理连接的新函数。在这个新的 `handle_connection` 函数中,咱们将从 TCP 流读取数据,并将其打印出来,从而咱们就可以看到从浏览器发出的数据。请将代码修改为清单 20-2 这样。
+我们来实现读取来自浏览器的请求的功能!为了将首先获取连接,然后对连接采取一些操作的关注点分离,我们将开启一个新函数来处理连接。在这个新的 `handle_connection` 函数中,我们将读取 TCP 流中的数据并将其打印出来,以便我们可以查看浏览器发送的数据。请修改代码为下面清单 21-2 这样。
+
文件名:`src/main.rs`
```rust
-#![allow(warnings)]
use std::{
- io::{prelude::*, BufReader},
+ io::{BufReader, prelude::*},
net::{TcpListener, TcpStream},
};
@@ -91,87 +98,83 @@ fn main() {
for stream in listener.incoming() {
let stream = stream.unwrap();
- handle_conn(stream);
+ handle_connection(stream);
}
}
-fn handle_conn(mut stream: TcpStream) {
- let buf_reader = BufferedReader::new(stream);
- let http_req: Vec<_> = buf_reader
+fn handle_connection(mut stream: TcpStream) {
+ let buf_reader = BufReader::new(&stream);
+ let http_request: Vec<_> = buf_reader
.lines()
- .map(|res| res.unwrap())
+ .map(|result| result.unwrap())
.take_while(|line| !line.is_empty())
.collect();
- println! ("请求:{:#?}", http_request);
+ println!("Request: {http_request:#?}");
}
```
-*清单 20-2:从 `TcpStream` 读取并打印出数据*
+**清单 21-2**:从 `TcpStream` 读取并打印数据
+我们带入 `std::io::prelude` 和 `std::io::BufReader` 到作用域,以访问允许我们从流中读取和向流中写入的特质与类型。在 `main` 函数的 `for` 循环中,我们不再打印表示我们已建立连接的消息,而是调用新的 `handle_connection` 函数,并传递 `stream` 给他。
-咱们将 `std::io::prelude` 与 `std::io::BufReader` 带入作用域,来获取到实现从 TCP 流读取和写入的那些特质与类型的访问。在 `main` 函数的那个 `for` 循环中,不再是打印一条声称咱们已构造一个连接的消息,咱们限制调用了新的 `handle_conn` 函数,并把那个 `stream` 传递给他。
+在 `handle_connection` 函数中,我们创建了一个新的 `BufReader` 实例,封装了到 `stream` 的引用。`BufReader` 通过管理到 `std::io::Read` 特质方法的调用,为我们带来缓冲功能。
-在 `handle_conn` 函数中,咱们创建了一个新的,封装着到 `stream` 的一个可变引用的 `BufReader` 实例。`BufReader` 会通过管理到 `std::io::Read` 特质一些方法的调用,为咱们添加缓冲。
+我们创建了一个名为 `http_request` 的变量,以收集浏览器发送到我们服务器的请求行。通过添加 `Vec<_>` 的类型注解,我们表明希望收集这些行到一个矢量值中。
-咱们创建了一个名为 `http_req` 的变量,来收集浏览器发送到咱们服务器的请求的那些行。通过添加那个 `Vec<_>` 类型注解,咱们表明了咱们打算把这些行收集到一个矢量值中。
+`BufReader` 实现了 `std::io::BufRead` 特质,该特质提供了 `lines` 方法。`lines` 方法通过在遇到新行字节时分割数据流,返回一个 `Result` 类型的迭代器。为了获取每个 `String`,我们 `map` 并 `unwrap` 每个 `Result`。当数据不是有效的 UTF-8 编码,或者从流读取存在问题时,`Result` 可能就是错误。同样,生产程序应该更优雅地处理这些错误,但为了简单起见,我们选择了在错误情形下停止程序。
-`BufReader` 实现了 `std::io::BufRead` 特质,该特质提供了 `lines` 方法。`lines` 方法会经由当其发现一个新行字节,a newline byte, 时分割数据流,而返回一个 `Result **译注**:使用 `curl 127.0.0.1:7878` 的输出,如下面这样:
+>
+> ```console
+> $ cargo run
+> Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s
+> Running `target/debug/hello`
+> Request: [
+> "GET / HTTP/1.1",
+> "Host: 127.0.0.1:7878",
+> "User-Agent: curl/8.19.0",
+> "Accept: */*",
+> ]
+> ```
-> 注:使用 `curl --noproxy '*' 127.0.0.1:7878` 的输出,如下面这样:
+根据咱们的浏览器,咱们可能会得到略有不同的输出。现在我们正在打印请求数据,通过查看请求第一行中 `GET` 之后的路径,我们可以发现为何我们会从一次浏览器请求得到多个连接。当重复的连接都在请求 `/`,我们就知道浏览器正在重复获取 `/`,因为他没有从我们的程序得到响应。
+
+我们来分写这一请求数据,以了解浏览器向我们的程序请求什么。
-```console
-$ cargo run
- Finished dev [unoptimized + debuginfo] target(s) in 0.00s
- Running `target/debug/hello`
-请求:[
- "GET / HTTP/1.1",
- "Host: 127.0.0.1:7878",
- "User-Agent: curl/7.68.0",
- "Accept: */*",
-]
-```
+## 仔细观察 HTTP 请求
-根据咱们的浏览器,咱们可能会得到些许不同的输出。既然咱们打印了请求数据,咱们就可以通过查看请求第一行中 `GET` 之后的路径,而发现为何咱们会从一次浏览器请求,得到多个连接。若重复的连接都是在请求 `/`,咱们就知道由于浏览器没有从咱们的程序得到响应,因此其是在尝试重复获取 `/`。
-
-下面来对这一请求数据加以细分,以搞清楚浏览器是在询问咱们的程序些什么。
-
-
-## 近观 HTTP 请求
-
-**A closer Look at an HTTP Request**
-
-
-HTTP 是种基于文本的协议,而请求会采用下面这种格式:
+HTTP 属于基于文本的协议,请求采用以下格式:
```text
Method Request-URI HTTP-Version CRLF
@@ -179,39 +182,35 @@ headers CRLF
message-body
```
-第一行是保存着有关该客户端正请求什么的信息的 *请求行,request line*。该请求行的第一部分,表示正使用的 *方法,method*,比如 `GET` 或 `POST`,描述了客户端是如何构造此请求的。咱们的客户端使用了一个 `GET` 请求,这意味着其是在询问信息。
+第一行是 *请求行*,包含有关客户端请求的信息。请求行的第一部分指明了所使用的方法,比如 `GET` 或 `POST`,这描述了客户端是发出请求的方式。我们的客户端使用了 `GET` 请求,这意味着他正在请求信息。
-请求行接下来的部分为 `/`,表示客户端正请求的 *同一资源标识符,Uniform Resource Identifier, URI*:URI 几乎是,但不完全与 *同一资源定位符,Uniform Resource Locator, URL* 一样。URIs 与 URLs 之间的区别对于这章中咱们的目的不重要,但 HTTP 的规格使用了 URI 这个词,因此咱们只能在此处暗自用 URL 代替 URI。
+请求行的下一部分是 `/`,表示客户端正在请求的 *同一资源标识符,URI*:URI 与 *统一资源定位符,URL* 几乎相同。URI 与 URL 之间的区别对于我们这一章的目的来说不重要,但 HTTP 规范使用 URI 这一术语,因此我们在这里可以将 *URL* 理解为 *URI*。
-最后部分是客户端所用的 HTTP 版本,而随后这个请求行便以一个 *CRLF 序列,CRLF sequence* (CRLF 代表的是 *回车,carriage return* 与 *换行,line fedd*,是打字机时代的术语!)结束了。这个 CRLF 序列还可以写作 `\r\n`,其中的 `\r` 是个回车,而 `\n` 是个换行。CRLF 序列将请求行与其余的请求数据分开。请注意当 CRLF 被打印时,咱们会看到一个新行开始,而非 `\r\n`。
+最后一部分是客户端使用的 HTTP 版本,随后请求行以 CRLF 序列结束。(CRLF 代表 *回车,carriage return* 和 *换行,line fedd*,这是打字机时代的术语!)CRLF 序列也可以写成 `\r\n`,其中的 `\r` 是回车符,`\n` 是换行符。CRLF 序列将请求行与请求数据的其余部分分开。请注意,当打印 CRLF 时,我们会看到一个新行开始,而不是 `\r\n`。
-查看如今咱们从运行这个程序所接收到的请求行数据,咱们发现 `GET` 即为请求方法,`/` 便是请求的 URI,而 `HTTP/1.1` 则是请求的 HTTP 版本。
+查看到目前为止运行程序所接收的请求行数据,我们可以看到,`GET` 是请求方法,`/` 是请求 URI,`HTTP/1.1` 是版本。
-在请求行之后,从 `Host:` 开始的其余那些行,就是些头了。`GET` 请求没有请求体。
+在请求行之后,从 `Host:` 开始的其余行都属于头部。`GET` 请求没有请求体。
-请从不同浏览器构造请求,或是询问不同地址,比如 `127.0.0.1:7878/test`,来发现请求数据会怎样变化。
+请尝试从不同浏览器发出请求,或是请求不同的地址,比如 `127.0.0.1:7878/test`,以查看请求数据如何变化。
-> 注:运行 `curl --noproxy '*' 127.0.0.1:7878/test` 时,请求数据如下所示:
+> **译注**:运行 `curl 127.0.0.1:7878/test` 时,请求数据如下所示:
+>
+> ```console
+> Request: [
+> "GET /test HTTP/1.1",
+> "Host: 127.0.0.1:7878",
+> "User-Agent: curl/8.19.0",
+> "Accept: */*",
+> ]
+> ```
+
+现在我们知道浏览器请求了什么,我们就来发回一些数据!
-```console
-请求:[
- "GET /test HTTP/1.1",
- "Host: 127.0.0.1:7878",
- "User-Agent: curl/7.68.0",
- "Accept: */*",
-]
-```
+## 编写响应
-既然咱们明白了浏览器是在询问什么,下面就来发回一些数据吧!
-
-
-## 写下响应
-
-**Writing a Response**
-
-
-咱们将要实现发送响应客户端请求数据。响应有着下面的格式:
+我们将实现发送数据以响应客户端请求。响应有着以下格式:
```text
HTTP-Version Status-Code Reason-Phrase CRLF