mirror of
https://github.com/apachecn/eloquent-js-3e-zh.git
synced 2025-05-29 08:12:22 +00:00
20.
This commit is contained in:
parent
8c337da955
commit
34abc5f388
209
20.md
209
20.md
@ -146,36 +146,38 @@ $ node
|
|||||||
|
|
||||||
## 版本
|
## 版本
|
||||||
|
|
||||||
也许有些出乎意料,figlet.text并不是简单地返回一个组成巨大字母的字符串,而会接受一个回调函数,并将结果传递给该函数。该函数也会向回调函数传递第二个参数即error,在发生一些错误时用该参数保存一个错误对象,若一切正常则为null。
|
`package.json`文件列出了程序自己的版本和它的依赖的版本。 版本是一种方式,用于处理软件包单独演变。为使用某个时候的软件包而编写的代码,可能不能使用软件包的更高版本。
|
||||||
|
|
||||||
这在Node代码中是常见模式。使用figlet渲染字符需要读取包含字符形状的文件,而在Node中读取文件是一个异步操作,因此figlet.text无法立即返回结果。在某种程度上,异步性是具有传染性的。每个调用异步函数的函数都会变成一个异步函数。
|
NPM 要求其软件包遵循名为语义版本控制(semantic versioning)的纲要,它编码了版本号中的哪些版本是兼容的(不破坏就接口)。 语义版本由三个数字组成,用点分隔,例如`2.3.0`。 每次添加新功能时,中间数字都必须递增。 每当破坏兼容性时,使用该软件包的现有代码可能不适用于新版本,因此必须增加第一个数字。
|
||||||
|
|
||||||
NPM并不只是npm install那么简单,该工具会读取package.json文件,该文件包含了以JSON格式编码的程序或库的信息(比如依赖于哪些库)。在包含该文件的目录下执行nmp install,会自动安装所有的依赖,还包括依赖的依赖。NPM工具也可用来将库发布到NPM在线包仓库中,这样其他人就可以查找、下载并使用它们。
|
`package.json`中的依赖项版本号前面的脱字符(`^`),表示可以安装兼容给定编号的任何版本。 例如`"^2.3.0"`意味着任何大于等于`2.3.0`且小于`3.0.0`的版本都是允许的。
|
||||||
|
|
||||||
本书不会继续深入探究NPM的使用细节。请读者自行参考[http://npmjs.org/](http://npmjs.org/)进一步阅读文档,也可以在该网站中轻松地搜索库。
|
`npm`命令也用于发布新软件包或软件包的新版本。 如果你在一个包含`package.json`文件的目录中执行`npm publish`,它将一个包发布到注册处,带有 JSON 文件中列出的名称和版本。 任何人都可以将软件包发布到 NPM - 但只能用新名称,因为任何人可以更新现有的软件包,会有点恐怖。
|
||||||
|
|
||||||
|
由于`npm`程序是与开放系统(软件包注册处)进行对话的软件,因此它没有什么独特之处。 另一个程序`yarn`,可以从 NPM 注册处中安装,使用一种不同的接口和安装策略,与`npm`具有相同的作用。
|
||||||
|
|
||||||
|
本书不会深入探讨 NPM 的使用细节。 请参阅 [npmjs.org](https://npmjs.org) 来获取更多文档和搜索软件包的方法。
|
||||||
|
|
||||||
### 20.6 文件系统模块
|
### 20.6 文件系统模块
|
||||||
|
|
||||||
在Node中最常用的内建模块就是“fs”(表示Filesystem,文件系统)模块。该模块提供了处理文件和目录的函数。
|
在Node中最常用的内建模块就是“fs”(表示Filesystem,文件系统)模块。该模块提供了处理文件和目录的函数。
|
||||||
|
|
||||||
例如,有个函数名为readFile,该函数读取文件并调用回调函数,并将文件内容传递给回调函数。
|
例如,有个函数名为readFile,该函数读取文件并调用回调,并将文件内容传递给回调。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var fs = require("fs");
|
let {readFile} = require("fs");
|
||||||
fs.readFile("file.txt", "utf8", function(error, text) {
|
readFile("file.txt", "utf8", (error, text) => {
|
||||||
if (error)
|
if (error) throw error;
|
||||||
throw error;
|
console.log("The file contains:", text);
|
||||||
console.log("The file contained:", text);
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
readFile的第二个参数表示字符编码,用于将文件解码成字符串。将文本编码成二进制数据有许多方式,但现代系统中最为普遍的是使用UTF-8编码文本,因此除非有特殊原因确信文件使用了别的编码,否则读取文件时使用“utf-8”是一种较为安全的方式。若你不传递任何编码,Node会认为你需要解析二进制数据,因此会返回一个Buffer对象而非字符串。该对象类似于数组,每个元素是文件中字节对应的数字。
|
readFile的第二个参数表示字符编码,用于将文件解码成字符串。将文本编码成二进制数据有许多方式,但大多数现代系统使用 UTF-8,因此除非有特殊原因确信文件使用了别的编码,否则读取文件时使用“utf-8”是一种较为安全的方式。若你不传递任何编码,Node会认为你需要解析二进制数据,因此会返回一个Buffer对象而非字符串。该对象类似于数组,每个元素是文件中字节(数据的 8 位的块)对应的数字。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var fs = require("fs");
|
const {readFile} = require("fs");
|
||||||
fs.readFile("file.txt", function(error, buffer) {
|
readFile("file.txt", (error, buffer) => {
|
||||||
if (error)
|
if (error) throw error;
|
||||||
throw error;
|
|
||||||
console.log("The file contained", buffer.length, "bytes.",
|
console.log("The file contained", buffer.length, "bytes.",
|
||||||
"The first byte is:", buffer[0]);
|
"The first byte is:", buffer[0]);
|
||||||
});
|
});
|
||||||
@ -184,12 +186,10 @@ fs.readFile("file.txt", function(error, buffer) {
|
|||||||
有一个名为writeFile的函数与其类似,用于将文件写到磁盘上。
|
有一个名为writeFile的函数与其类似,用于将文件写到磁盘上。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var fs = require("fs");
|
const {writeFile} = require("fs");
|
||||||
fs.writeFile("graffiti.txt", "Node was here", function(err) {
|
writeFile("graffiti.txt", "Node was here", err => {
|
||||||
if (err)
|
if (err) console.log(`Failed to write file: ${err}`);
|
||||||
console.log("Failed to write file:", err);
|
else console.log("File written.");
|
||||||
else
|
|
||||||
console.log("File written.");
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -197,29 +197,41 @@ fs.writeFile("graffiti.txt", "Node was here", function(err) {
|
|||||||
|
|
||||||
“fs”模块也包含了其他实用函数,其中readdir函数用于将目录中的文件以字符串数组的方式返回,stat函数用于获取文件信息,rename函数用于重命名文件,unlink用于删除文件等。
|
“fs”模块也包含了其他实用函数,其中readdir函数用于将目录中的文件以字符串数组的方式返回,stat函数用于获取文件信息,rename函数用于重命名文件,unlink用于删除文件等。
|
||||||
|
|
||||||
|
而且其中大多数都将回调作为最后一个参数,它们会以错误(第一个参数)或成功结果(第二个参数)来调用。 我们在第十一章中看到,这种编程风格存在缺点 - 最大的缺点是,错误处理变得冗长且容易出错。
|
||||||
|
|
||||||
相关细节请参见[http://nodejs.org/](http://nodejs.org/)中的文档。
|
相关细节请参见[http://nodejs.org/](http://nodejs.org/)中的文档。
|
||||||
|
|
||||||
fs模块中的许多函数都有异步与同步的两种变体。例如,函数readFile的同步版本名为readFileSync。
|
虽然`Promise`已经成为 JavaScript 的一部分,但是,将它们与 Node.js 的集成的工作仍然还在进行中。 从 v10 开始,标准库中有一个名为`fs/promises`的包,它导出的函数与`fs`大部分相同,但使用`Promise`而不是回调。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var fs = require("fs");
|
const {readFile} = require("fs/promises");
|
||||||
console.log(fs.readFileSync("file.txt", "utf8"));
|
readFile("file.txt", "utf8")
|
||||||
|
.then(text => console.log("The file contains:", text));
|
||||||
```
|
```
|
||||||
|
|
||||||
使用同步函数较为节省代码,在简单的脚本中非常实用(不需要异步I/O提供的额外速度提升)。但需要注意的是当执行同步操作时,程序会完全停止。若程序需要对用户或网络上的其他机器做出响应,可能因为同步I/O而产生令人厌烦的延迟。
|
有时候你不需要异步,而是需要阻塞。 `fs`中的许多函数也有同步的变体,它们的名称相同,末尾加上`Sync`。 例如,`readFile`的同步版本称为`readFileSync`。
|
||||||
|
|
||||||
|
```
|
||||||
|
const {readFileSync} = require("fs");
|
||||||
|
console.log("The file contains:",
|
||||||
|
readFileSync("file.txt", "utf8"));
|
||||||
|
```
|
||||||
|
|
||||||
|
请注意,在执行这样的同步操作时,程序完全停止。 如果它应该响应用户或网络中的其他计算机,那么可在同步操作中可能会产生令人讨厌的延迟。
|
||||||
|
|
||||||
### 20.7 HTTP模块
|
### 20.7 HTTP模块
|
||||||
|
|
||||||
另一个主要模块名为"http"。该模块提供了执行HTTP服务和产生HTTP请求的功能。
|
另一个主要模块名为"http"。该模块提供了执行HTTP服务和产生HTTP请求的功能。
|
||||||
|
|
||||||
启动一个简单的HTTP服务器只需要以下代码。
|
启动一个HTTP服务器只需要以下代码。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var http = require("http");
|
const {createServer} = require("http");
|
||||||
var server = http.createServer(function(request, response) {
|
let server = createServer((request, response) => {
|
||||||
response.writeHead(200, {"Content-Type": "text/html"});
|
response.writeHead(200, {"Content-Type": "text/html"});
|
||||||
response.write("<h1>Hello!</h1><p>You asked for <code>" +
|
response.write(`
|
||||||
request.url + "</code></p>");
|
<h1>Hello!</h1>
|
||||||
|
<p>You asked for <code>${request.url}</code></p>`);
|
||||||
response.end();
|
response.end();
|
||||||
});
|
});
|
||||||
server.listen(8000);
|
server.listen(8000);
|
||||||
@ -229,123 +241,128 @@ server.listen(8000);
|
|||||||
|
|
||||||
每次客户端尝试连接服务器时,服务器都会调用传递给createServer函数的参数。request和response绑定都是对象,分别表示输入数据和输出数据。request包含请求信息,例如该对象的url属性表示请求的URL。
|
每次客户端尝试连接服务器时,服务器都会调用传递给createServer函数的参数。request和response绑定都是对象,分别表示输入数据和输出数据。request包含请求信息,例如该对象的url属性表示请求的URL。
|
||||||
|
|
||||||
你需要调用response对象的方法以将一些数据发回客户端。第一个函数调用(writeHead)会输出响应头(参见第17章)。你需要向该函数传递状态码(本例中200表示成功)和一个对象,该对象包含头部信息的值。本例中我们告诉客户端我们送回的是HTML文档。
|
因此,当您在浏览器中打开该页面时,它会向您自己的计算机发送请求。 这会导致服务器函数运行并返回一个响应,您可以在浏览器中看到该响应。
|
||||||
|
|
||||||
接下来使用response.write来发送响应体(文档自身)。若你想一段一段地发送相应信息,可以多次调用该方法,在合适的时候会将数据发送到客户端。最后调用response.end发送相应结束信号。
|
你需要调用response对象的方法以将一些数据发回客户端。第一个函数调用(writeHead)会输出响应头(参见第17章)。你需要向该函数传递状态码(本例中200表示成功)和一个对象,该对象包含头部信息的值。该示例设置了“Content-Type”头,通知客户端我们将发送一个HTML文档。
|
||||||
|
|
||||||
调用server.listen会使服务器在8000端口上开始等待请求。这就是与服务器通信时,需要连接localhost:8000,而不是localhost(这样将会使用默认端口,即80)的原因。
|
接下来使用response.write来发送响应体(文档自身)。若你想一段一段地发送相应信息,可以多次调用该方法,例如将数据发送到客户端。最后调用response.end发送相应结束信号。
|
||||||
|
|
||||||
这类Node脚本不会自动结束,因为它会永远等待下一个事件(在本例中是网络连接),所以需要按Ctrl-C来停止脚本。
|
调用server.listen会使服务器在8000端口上开始等待请求。这就是你需要连接localhost:8000和服务器通信,而不是localhost(这样将会使用默认端口,即80)的原因。
|
||||||
|
|
||||||
一个真实的Web服务器需要做的事情比上面的示例多得多。其差别在于我们需要根据请求的方法(method属性),来判断客户端尝试执行的动作,并根据请求的URL来找出动作处理的资源。本章随后会介绍更高级的服务器。
|
当你运行这个脚本时,这个进程就在那里等着。 当一个脚本正在监听事件时 - 这里是网络连接 - Node 不会在到达脚本末尾时自动退出。为了关闭它,请按 Ctrl-C。
|
||||||
|
|
||||||
你可以使用http模块的request函数来充当一个HTTP客户端。
|
一个真实的Web服务器需要做的事情比示例多得多。其差别在于我们需要根据请求的方法(method属性),来判断客户端尝试执行的动作,并根据请求的URL来找出动作处理的资源。本章随后会介绍更高级的服务器。
|
||||||
|
|
||||||
|
我们可以使用http模块的request函数来充当一个HTTP客户端。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var http = require("http");
|
const {request} = require("http");
|
||||||
var request = http.request({
|
let requestStream = request({
|
||||||
hostname: "eloquentjavascript.net",
|
hostname: "eloquentjavascript.net",
|
||||||
path: "/20_node.html",
|
path: "/20_node.html",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {Accept: "text/html"}
|
headers: {Accept: "text/html"}
|
||||||
}, function(response) {
|
}, response => {
|
||||||
console.log("Server responded with status code",
|
console.log("Server responded with status code",
|
||||||
response.statusCode);
|
response.statusCode);
|
||||||
});
|
});
|
||||||
request.end();
|
requestStream.end();
|
||||||
```
|
```
|
||||||
|
|
||||||
request函数的第一个参数是请求配置,告知Node需要访问的服务器、服务器请求地址、使用的方法等信息。第二个参数是响应开始时的回调函数。该回调函数会接受一个参数,用于检查相应信息,例如获取状态码。
|
request函数的第一个参数是请求配置,告知Node需要访问的服务器、服务器请求地址、使用的方法等信息。第二个参数是响应开始时的回调。该回调会接受一个参数,用于检查相应信息,例如获取状态码。
|
||||||
|
|
||||||
和在服务器中看到的response对象一样,request返回的对象允许我们使用write方法多次发送数据,并使用end方法结束发送。本例中并没有使用wirte方法,因为GET请求的请求体中无法包含数据。
|
和在服务器中看到的response对象一样,request返回的对象允许我们使用write方法多次发送数据,并使用end方法结束发送。本例中并没有使用wirte方法,因为GET请求的请求正文中无法包含数据。
|
||||||
|
|
||||||
Node提供了名为https的包,用于发送安全的HTTP(HTTPS)请求,其中包含了自己的request函数,与http.request类似。
|
`https`模块中有类似的`request`函数,可以用来向`https:` URL 发送请求。
|
||||||
|
|
||||||
|
但是使用 Node 的原始功能发送请求相当麻烦。 NPM 上有更多方便的包装包。 例如,`node-fetch`提供了我们从浏览器得知的,基于`Promise`的`fetch`接口。
|
||||||
|
|
||||||
### 20.8 流
|
### 20.8 流
|
||||||
|
|
||||||
我们在HTTP中看过两个可写流的例子,即服务器可以向response对象中写入数据,而http.request返回的请求对象也可以写入数据。
|
我们在HTTP中看过两个可写流的例子,即服务器可以向response对象中写入数据,而request返回的请求对象也可以写入数据。
|
||||||
|
|
||||||
可写流是Node接口中广泛使用的概念。所有的可写流都有一个write方法,你可以传递字符串或Buffer对象。可写流的end方法用于关闭流,如果给定一个参数,该方法会在关闭流前输出指定的一段数据。这两个方法都可以使用一个回调函数作为额外参数,当写入数据或关闭流完成后,会调用用户指定的回调函数。
|
可写流是Node中广泛使用的概念。这种对象有一个write方法,你可以传递字符串或Buffer对象,来向流写入一些数据。它们end方法用于关闭流,并且还可以接受一个可选值,在流关闭之前将其写入流。 这两个方法也可以接受回调作为附加参数,当写入或关闭完成时它们将被调用。
|
||||||
|
|
||||||
我们也可以使用fs.createWriteStream建立一个指向本地文件的输出流。你可以调用该方法返回的结果对象的write方法,每次向文件中写入一段数据,而不是像fs.writeFile那样一次性写入所有数据。
|
我们也可以使用来自fs模块的createWriteStream,建立一个指向本地文件的输出流。你可以调用该方法返回的结果对象的write方法,每次向文件中写入一段数据,而不是像fs.writeFile那样一次性写入所有数据。
|
||||||
|
|
||||||
可读流则略为复杂。传递给HTTP服务器回调函数的request绑定,以及传递给HTTP客户端回调函数的response对象都是可读流(服务器读取请求并写入响应,而客户端则先写入请求,然后读取响应)。读取流需要使用事件处理器,而不是方法。
|
可读流则略为复杂。传递给HTTP服务器回调的request绑定,以及传递给HTTP客户端回调的response对象都是可读流(服务器读取请求并写入响应,而客户端则先写入请求,然后读取响应)。读取流需要使用事件处理器,而不是方法。
|
||||||
|
|
||||||
Node中发出的事件都有一个on方法,类似浏览器中的addEventListener方法。该方法接受一个事件名和一个函数,并将函数注册到事件上,接下来每当指定事件发生时,都会调用注册的函数。
|
Node中发出的事件都有一个on方法,类似浏览器中的addEventListener方法。该方法接受一个事件名和一个函数,并将函数注册到事件上,接下来每当指定事件发生时,都会调用注册的函数。
|
||||||
|
|
||||||
可读流有data事件和end事件。data事件在每次数据到来时触发,end事件在流结束时触发。该模型适用于“流”数据,这类数据可以立即处理,即使整个文档的数据没有到位。我们可以使用fs.createReadStream函数创建一个可读流,来读取本地文件。
|
可读流有data事件和end事件。data事件在每次数据到来时触发,end事件在流结束时触发。该模型适用于“流”数据,这类数据可以立即处理,即使整个文档的数据没有到位。我们可以使用fs.createReadStream函数创建一个可读流,来读取本地文件。
|
||||||
|
|
||||||
下面的代码创建了一个服务器并读取请求体,然后将读取到的数据全部转换成大写,并使用流写回客户端。
|
这段代码创建了一个服务器并读取请求正文,然后将读取到的数据全部转换成大写,并使用流写回客户端。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var http = require("http");
|
const {createServer} = require("http");
|
||||||
http.createServer(function(request, response) {
|
createServer((request, response) => {
|
||||||
response.writeHead(200, {"Content-Type": "text/plain"});
|
response.writeHead(200, {"Content-Type": "text/plain"});
|
||||||
request.on("data", function(chunk) {
|
request.on("data", chunk =>
|
||||||
response.write(chunk.toString().toUpperCase());
|
response.write(chunk.toString().toUpperCase()));
|
||||||
});
|
request.on("end", () => response.end());
|
||||||
request.on("end", function() {
|
|
||||||
response.end();
|
|
||||||
});
|
});
|
||||||
}).listen(8000);
|
}).listen(8000);
|
||||||
```
|
```
|
||||||
|
|
||||||
传递给data处理函数的chunk绑定是一个二进制Buffer对象,我们可以使用toString将其转换成字符串,该方法会使用默认编码(UTF-8)解码二进制数据。
|
传递给data处理函数的chunk值是一个二进制Buffer对象,我们可以使用它的`toString`方法,通过将其解码为 UTF-8 编码的字符,来将其转换为字符串。
|
||||||
|
|
||||||
下面是另一段代码,当我们执行上面的服务(将字母转换成大写)时,这段代码会向服务器发送一个请求并输出获取到的响应数据:
|
下面的一段代码,和上面的服务(将字母转换成大写)一起运行时,它会向服务器发送一个请求并输出获取到的响应数据:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var http = require("http");
|
const {request} = require("http");
|
||||||
var request = http.request({
|
request({
|
||||||
hostname: "localhost",
|
hostname: "localhost",
|
||||||
port: 8000,
|
port: 8000,
|
||||||
method: "POST"
|
method: "POST"
|
||||||
}, function(response) {
|
}, response => {
|
||||||
response.on("data", function(chunk) {
|
response.on("data", chunk =>
|
||||||
process.stdout.write(chunk.toString());
|
process.stdout.write(chunk.toString()));
|
||||||
});
|
}).end("Hello server");
|
||||||
});
|
// → HELLO SERVER
|
||||||
request.end("Hello server");
|
|
||||||
```
|
```
|
||||||
|
|
||||||
该示例代码向process.stdout(进程的标准输出流,也是一个可写流)中写入数据,而不使用console.log,因为console.log函数会在输出的每段文本后加上额外的换行符,在这里不太合适。
|
该示例代码向process.stdout(进程的标准输出流,是一个可写流)中写入数据,而不使用console.log,因为console.log函数会在输出的每段文本后加上额外的换行符,在这里不太合适。
|
||||||
|
|
||||||
### 20.9 简单的文件服务器
|
### 文件服务器
|
||||||
|
|
||||||
让我们将新学习的HTTP服务器和文件系统的知识结合起来,并建立起两者之间的桥梁:使用HTTP服务允许客户远程访问文件系统。这个服务有许多用处,它允许网络应用程序存储并共享数据或使得一组人可以共享访问一批文件。
|
让我们结合新学习的HTTP服务器和文件系统的知识,并建立起两者之间的桥梁:使用HTTP服务允许客户远程访问文件系统。这个服务有许多用处,它允许网络应用程序存储并共享数据或使得一组人可以共享访问一批文件。
|
||||||
|
|
||||||
当我们将文件当作HTTP资源时,可以将HTTP的GET、PUT和DELETE方法分别看成读取、写入和删除文件。我们将请求中的路径解释成请求指向的文件路径。
|
当我们将文件当作HTTP资源时,可以将HTTP的GET、PUT和DELETE方法分别看成读取、写入和删除文件。我们将请求中的路径解释成请求指向的文件路径。
|
||||||
|
|
||||||
我们可能不希望共享整个文件系统,因此我们将这些路径解释成以服务器工作路径(即启动服务器的路径)为起点的相对路径。若从/home/marijn/public(或Windows下的C:\Users\marijn\public)启动服务器,那么对/file.txt的请求应该指向/home/marijn/public/file.txt(或C:\Users\marijn\public\file.txt)。
|
我们可能不希望共享整个文件系统,因此我们将这些路径解释成以服务器工作路径(即启动服务器的路径)为起点的相对路径。若从/home/marijn/public(或Windows下的C:\Users\marijn\public)启动服务器,那么对/file.txt的请求应该指向/home/marijn/public/file.txt(或C:\Users\marijn\public\file.txt)。
|
||||||
|
|
||||||
我们将一段段地构建程序,使用名为methods的对象来存储处理多种HTTP方法的函数。
|
我们将一段段地构建程序,使用名为methods的对象来存储处理多种HTTP方法的函数。方法处理器是`async`函数,它接受请求对象作为参数并返回一个`Promise`,解析为描述响应的对象。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var http = require("http"), fs = require("fs");
|
const {createServer} = require("http");
|
||||||
|
|
||||||
var methods = Object.create(null);
|
const methods = Object.create(null);
|
||||||
|
|
||||||
http.createServer(function(request, response) {
|
createServer((request, response) => {
|
||||||
function respond(code, body, type) {
|
let handler = methods[request.method] || notAllowed;
|
||||||
if (!type) type = "text/plain";
|
handler(request)
|
||||||
response.writeHead(code, {"Content-Type": type});
|
.catch(error => {
|
||||||
if (body && body.pipe)
|
if (error.status != null) return error;
|
||||||
body.pipe(response);
|
return {body: String(error), status: 500};
|
||||||
else
|
})
|
||||||
response.end(body);
|
.then(({body, status = 200, type = "text/plain"}) => {
|
||||||
}
|
response.writeHead(status, {"Content-Type": type});
|
||||||
if (request.method in methods)
|
if (body && body.pipe) body.pipe(response);
|
||||||
methods[request.method](urlToPath(request.url),
|
else response.end(body);
|
||||||
respond, request);
|
});
|
||||||
else
|
|
||||||
respond(405, "Method " + request.method +
|
|
||||||
" not allowed.");
|
|
||||||
}).listen(8000);
|
}).listen(8000);
|
||||||
|
|
||||||
|
async function notAllowed(request) {
|
||||||
|
return {
|
||||||
|
status: 405,
|
||||||
|
body: `Method ${request.method} not allowed.`
|
||||||
|
};
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
这样启动服务器之后,服务器永远只会产生405错误响应,该代码表示服务器无法处理特定的方法。
|
这样启动服务器之后,服务器永远只会产生405错误响应,该代码表示服务器拒绝处理特定的方法。
|
||||||
|
|
||||||
函数respond被传递给处理不同方法的函数,作为请求结束时的回调函数。该方法接受三个参数,第一个是状态码,第二个是响应体,第三个是可选的内容类型。若传递的响应体是一个可读流,该对象有一个pipe方法,用于将可读流转发到可写流中。若不是可读流,我们假定它是null(没有响应体)或一个字符串,我们将字符串直接传递给响应的end方法。
|
函数respond被传递给处理不同方法的函数,作为请求结束时的回调。该方法接受三个参数,第一个是状态码,第二个是响应体,第三个是可选的内容类型。若传递的响应体是一个可读流,该对象有一个pipe方法,用于将可读流转发到可写流中。若不是可读流,我们假定它是null(没有响应体)或一个字符串,我们将字符串直接传递给响应的end方法。
|
||||||
|
|
||||||
为了从请求的URL中获取路径,urlToPath函数使用了Node内建的url模块来解析URL。我们取出parse函数返回值中的pathname属性(结果类似于/file.txt),并进行解码,去除%20这类转义代码,并加上一个句号作为前缀,以产生相对于当前目录的路径。
|
为了从请求的URL中获取路径,urlToPath函数使用了Node内建的url模块来解析URL。我们取出parse函数返回值中的pathname属性(结果类似于/file.txt),并进行解码,去除%20这类转义代码,并加上一个句号作为前缀,以产生相对于当前目录的路径。
|
||||||
|
|
||||||
@ -394,7 +411,7 @@ methods.GET = function(path, respond) {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
因为fs.stat访问磁盘需要耗费一些时间,因此该函数是异步的。当文件不存在时,fs.stat会传递一个错误对象(包含code属性,值为“ENOENT”)给回调函数。若Node为不同类型的错误定义了不同的Error子类型,那是非常好的,但现在不存在这种类型。
|
因为fs.stat访问磁盘需要耗费一些时间,因此该函数是异步的。当文件不存在时,fs.stat会传递一个错误对象(包含code属性,值为“ENOENT”)给回调。若Node为不同类型的错误定义了不同的Error子类型,那是非常好的,但现在不存在这种类型。
|
||||||
|
|
||||||
我们只能在这里使用晦涩难懂的,源于Unix的错误代码。
|
我们只能在这里使用晦涩难懂的,源于Unix的错误代码。
|
||||||
|
|
||||||
@ -434,7 +451,7 @@ function respondErrorOrNothing(respond) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
若HTTP响应不包含任何数据,我们可以用状态码204(无内容)来表示这种情况。因为我们需要提供回调函数来报告错误或在某些不同情况下返回204响应,因此我编写了函数respondErrorOrNothing来创建一个回调函数。
|
若HTTP响应不包含任何数据,我们可以用状态码204(无内容)来表示这种情况。因为我们需要提供回调来报告错误或在某些不同情况下返回204响应,因此我编写了函数respondErrorOrNothing来创建一个回调。
|
||||||
|
|
||||||
下面是PUT请求的处理函数。
|
下面是PUT请求的处理函数。
|
||||||
|
|
||||||
@ -455,7 +472,7 @@ methods.PUT = function(path, respond, request) {
|
|||||||
|
|
||||||
完整的服务器脚本可以从[http://eloquentjavascript.net/code/file_server.js](http://eloquentjavascript.net/code/file_server.js)获取。读者可以下载该脚本并使用Node启动你自己的文件服务器。当然你可以修改并扩展该脚本,以完成本章的习题或进行实验。
|
完整的服务器脚本可以从[http://eloquentjavascript.net/code/file_server.js](http://eloquentjavascript.net/code/file_server.js)获取。读者可以下载该脚本并使用Node启动你自己的文件服务器。当然你可以修改并扩展该脚本,以完成本章的习题或进行实验。
|
||||||
|
|
||||||
命令行工具curl在类Unix系统中得到广泛使用,可用于产生HTTP请求。接下来的会话用于简单测试我们的服务器。这里需要注意,-x用于设置请求方法,-d用于包含请求体。
|
命令行工具curl在类Unix系统中得到广泛使用,可用于产生HTTP请求。接下来的会话用于简单测试我们的服务器。这里需要注意,-x用于设置请求方法,-d用于包含请求正文。
|
||||||
|
|
||||||
```
|
```
|
||||||
$ curl http://localhost:8000/file.txt
|
$ curl http://localhost:8000/file.txt
|
||||||
@ -472,15 +489,15 @@ File not found
|
|||||||
|
|
||||||
### 20.10 错误处理
|
### 20.10 错误处理
|
||||||
|
|
||||||
在文件服务器代码中,有6个位置我们显式传递异常,我们不知道如何处理异常。因为异常不会直接传播到回调函数中而是使用参数传递给回调函数,因此我们不得不每次都显式处理这些异常。这完全抵消了异常处理的优势,换言之,我们无法将错误处理集中到一起。
|
在文件服务器代码中,有6个位置我们显式传递异常,我们不知道如何处理异常。因为异常不会直接传播到回调中而是使用参数传递给回调,因此我们不得不每次都显式处理这些异常。这完全抵消了异常处理的优势,换言之,我们无法将错误处理集中到一起。
|
||||||
|
|
||||||
当一些代码在系统中抛出一个异常会发生什么?由于我们并未使用任何的try块,因此exception会直接传播到调用栈顶部。在Node中,这会导致程序停止运行并输出异常信息(包括堆栈轨迹)到程序的标准错误流中。
|
当一些代码在系统中抛出一个异常会发生什么?由于我们并未使用任何的try块,因此exception会直接传播到调用栈顶部。在Node中,这会导致程序停止运行并输出异常信息(包括堆栈轨迹)到程序的标准错误流中。
|
||||||
|
|
||||||
这就意味着我们的服务器在服务器代码内遇到任何问题都会崩溃,而相对的,异步问题会通过参数传递给回调函数。若我们想要处理所有在请求处理中引发的异常,为了确保我们可以发送响应,我们需要在每个回调函数中添加try/catch块。
|
这就意味着我们的服务器在服务器代码内遇到任何问题都会崩溃,而相对的,异步问题会通过参数传递给回调。若我们想要处理所有在请求处理中引发的异常,为了确保我们可以发送响应,我们需要在每个回调中添加try/catch块。
|
||||||
|
|
||||||
这是无法工作的。许多Node程序都尽可能少使用异常,如果没有这种假设,当引发异常时,由于程序无法处理这些异常,正常的结果就是程序崩溃。
|
这是无法工作的。许多Node程序都尽可能少使用异常,如果没有这种假设,当引发异常时,由于程序无法处理这些异常,正常的结果就是程序崩溃。
|
||||||
|
|
||||||
另一种方法是使用Promise,第17章中已经有过介绍。Promise可以捕捉由回调函数引发的异常,并将其作为错误向外层传播。我们可以在Node中加载promise库,并使用其管理异步控制。几乎没有Node库集成了promise,但包裹这些异常对象太烦琐了。而NPM提供了优秀的promise模块,包含一个名为denodeify的函数,用于将诸如fs.readFile之类的异步函数转换成返回promise的函数。
|
另一种方法是使用Promise,第17章中已经有过介绍。Promise可以捕捉由回调引发的异常,并将其作为错误向外层传播。我们可以在Node中加载promise库,并使用其管理异步控制。几乎没有Node库集成了promise,但包裹这些异常对象太烦琐了。而NPM提供了优秀的promise模块,包含一个名为denodeify的函数,用于将诸如fs.readFile之类的异步函数转换成返回promise的函数。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var Promise = require("promise");
|
var Promise = require("promise");
|
||||||
@ -494,7 +511,7 @@ readFile("file.txt", "utf8").then(function(content) {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
为了进行比较,我基于promise编写了另一个版本的文件服务器,该文件服务器可以从[http://eloquentjavascript.net/code/file_server_promises.js](http://eloquentjavascript.net/code/file_server_promises.js)获取。由于现在函数可以直接返回其结果,而不必要调用回调函数,因此代码变得稍微优雅一点,而异常处理路径也变成隐式处理,而非显式处理。
|
为了进行比较,我基于promise编写了另一个版本的文件服务器,该文件服务器可以从[http://eloquentjavascript.net/code/file_server_promises.js](http://eloquentjavascript.net/code/file_server_promises.js)获取。由于现在函数可以直接返回其结果,而不必要调用回调,因此代码变得稍微优雅一点,而异常处理路径也变成隐式处理,而非显式处理。
|
||||||
|
|
||||||
这里列举出几行基于promise的文件服务器代码,展示两种程序设计风格的差别。
|
这里列举出几行基于promise的文件服务器代码,展示两种程序设计风格的差别。
|
||||||
|
|
||||||
@ -532,7 +549,7 @@ Node是一种优雅直接的系统,可以让我们在非浏览器环境中执
|
|||||||
|
|
||||||
NPM为你所能想到的功能(当然还有相当多你想不到的)提供了库,你可以通过执行简单的命令,获取并安装这些库。Node也附带了许多内建模块,包括fs模块(处理文件系统)、http模块(执行HTTP服务器并生成HTTP请求)。
|
NPM为你所能想到的功能(当然还有相当多你想不到的)提供了库,你可以通过执行简单的命令,获取并安装这些库。Node也附带了许多内建模块,包括fs模块(处理文件系统)、http模块(执行HTTP服务器并生成HTTP请求)。
|
||||||
|
|
||||||
Node中的所有输入输出都是异步的,除非你明确使用函数的同步变体,比如fs.readFileSync。使用者提供回调函数,Node会在适当的时候调用回调函数(比如I/O请求结束时)。
|
Node中的所有输入输出都是异步的,除非你明确使用函数的同步变体,比如fs.readFileSync。使用者提供回调,Node会在适当的时候调用回调(比如I/O请求结束时)。
|
||||||
|
|
||||||
### 20.12 习题
|
### 20.12 习题
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user