Eloquent JavaScript 笔记 十九:Node.js
来源:互联网 发布:怎么上传图片到淘宝 编辑:程序博客网 时间:2024/06/05 14:41
1. Background
可以略过。
2. Asynchronicity
讲同步和异步的基本原理,可以略过。
3. The Node Command
首先,访问 nodejs.org 网站,安装node.js。
3.1. 执行js文件:
创建一个文件 hello.js,文件内容:
var message = "Hello world";console.log(message);在命令行下,运行:
$ node hello.js
输出:
Hello world
在node.js 环境下,console.log() 输出到 stdout。
3.2. 运行 node的CLI:
$ node> 1 + 12> [-1, -2, -3].map(Math.abs)[1, 2, 3]> process.exit(0)$像console一样,process也是一个全局对象。用来控制当前CLI的当前进程。
3.3. 访问命令行参数:
创建文件 showarv.js,文件内容:
console.log(process.argv);没看错,就一行。 process.argv 是个数组,包含了命令行传入的参数。
$ node showargv.js one --and two["node", "/home/marijn/showargv.js", "one", "--and", "two"]
3.4. 全局变量
标准的JavaScript全局变量在node.js中都可以访问,例如:Array, Math, JSON 等。
Browser相关的全局变量就不能访问了,例如:document,alert等。
在Browser环境下的全局对象 window,在node.js 中变成了 global。
4. Modules
node.js 环境下 built-in 的功能比较少,很多功能需要额外安装module。
node.js 内置了 CommonJS module 系统。我们可以直接使用 require 包含modules。
4.1. require 参数:
1. require("/home/marijn/elife/run.js"); 绝对路径
2. require("./run.js"); 当前目录
3. require("../world/world.js"); 基于当前目录的相对路径
4. require("fs") 内置的module
5. require("elife") 安装在 node_modules/elife/ 目录下的module。 使用npm会把module安装在 node_modules 目录下。
4.2. 使用require引用当前目录下的module
创建module文件,garble.js:
module.exports = function(string) { return string.split("").map(function(ch) { return String.fromCharCode(ch.charCodeAt(0) + 5); }).join("");};创建main.js, 引用garble.js:
var garble = require("./garble");// Index 2 holds the first actual command-line argumentvar argument = process.argv[2];console.log(garble(argument));运行:
$ node main.js JavaScriptOf{fXhwnuy
5. Installing with NPM
NPM - Node Package Manager
当安装node.js时,同时也安装了npm。
$ npm install figlet
$ node
运行npm install,会在当前目录创建 node_modules 文件夹,下载的modules就保存在这个文件夹中。
注意上面的 figlet.text() 函数,它是一个异步函数,它需要访问 figlet.text 文件,搜索每个字母对应的图形。
I/O 操作通常是比较费时的,所以,都要做成异步函数。它的第二个参数是个function,当I/O执行完之后被调用。
这是node.js 的通用模式,异步 I/O 函数通常都是这个写法。
我们也可以写一个 package.json 文件,在其中配置多个module,以及相互之间的依赖规则。当运行 npm install 时,它会自动搜寻此文件。
npm 的详细使用方法在 npmjs.org 。
6. The File System Module
6.1. 使用node.js 内置的 fs 模块读取文件:
var fs = require("fs");fs.readFile("file.txt", "utf8", function(error, text) { if (error) throw error; console.log("The file contained:", text);});
readFile() 的第二个参数是文件编码,但三个参数是function,在I/O完成后被调用。
6.2. 读取二进制文件:
var fs = require("fs");fs.readFile("file.txt", function(error, buffer) { if (error) throw error; console.log("The file contained", buffer.length, "bytes.", "The first byte is:", buffer[0]);});不写文件编码,就是按二进制读取,buffer是个数组,按字节存储文件内容。
6.3. 写入文件:
var fs = require("fs");fs.writeFile("graffiti.txt", "Node was here", function(err) { if (err) console.log("Failed to write file:", err); else console.log("File written.");});
不指定文件编码,默认是utf8。
fs 模块还有好多方法。
6.4. 同步I/O
var fs = require("fs");console.log(fs.readFileSync("file.txt", "utf8"));
7. The HTTP Module
使用内置的 http 模块可以构建完整的 HTTP Server。 (哈哈,相当于 nginx + PHP)
7.1. 创建 http server:
var http = require("http");var server = http.createServer(function(request, response) { response.writeHead(200, {"Content-Type": "text/html"}); response.write("<h1>Hello!</h1><p>You asked for <code>" + request.url + "</code></p>"); response.end();});server.listen(8000);运行这个文件会让控制台阻塞。
每来一个request请求都会调用一次 createServer()。
7.2. 创建 http client:
var http = require("http");var req = { hostname: "eloquentjavascript.net", path: "/20_node.html", method: "GET", headers: {Accept: "text/html"}};var request = http.request(req, function(response) { console.log("Server responded with status code", response.statusCode);});request.end();建立HTTPS连接,使用 https 模块,基本功能和http一样。
8. Streams
8.1. writable stream
7.1 中的response 和 7.2中的 request 都有个write() 方法,可以多次调用此方法发送数据。这叫 writable stream。
6.3 中的writeFile() 方法不是stream,因为,调用一次就会把文件清空,重新写一遍。
fs 也有stream方法。使用fs.createWriteStream() 可以创建一个stream对象,在此对象上调用 write() 方法就可以像流那样写入了。
8.2. readable stream
server 端的request对象,和client端的response对象都是 readable stream。在event handler中,才能从stream中读取数据。
有 “data" , "end" 事件。
fs.createReadStream() 创建文件 readable stream。
8.3. on
类似于 addEventListener()
8.4. 例子:server
var http = require("http");http.createServer(function(request, response) { response.writeHead(200, {"Content-Type": "text/plain"}); request.on("data", function(chunk) { response.write(chunk.toString().toUpperCase()); }); request.on("end", function() { response.end(); });}).listen(8000);这是一个web server,把客户端发送来的字符串变成大写,再发送回去。chunk 是二进制buffer。
8.5. 例子:client
var http = require("http");var request = http.request({ hostname: "localhost", port: 8000, method: "POST"}, function(response) { response.on("data", function(chunk) { process.stdout.write(chunk.toString()); });});request.end("Hello server");如果 8.4 的server正在运行,执行这个文件会在控制台输入:HELLO SERVER
process.stdout() 也是一个 writable stream。
这里不能使用 console.log() ,因为它会在每一次调用后面加换行符。
9. A Simple File Server
9.1. File Server 说明
构建一个HTTP server,用户可以通过http request访问server上的文件系统。
GET 方法读取文件,PUT 方法写入文件,DELETE方法删除文件。
只能访问server运行的当前目录,不能访问整个文件系统。
9.2. server 骨架
var http = require("http"), fs = require("fs");var methods = Object.create(null);http.createServer(function(request, response) { function respond(code, body, type) { if (!type) type = "text/plain"; response.writeHead(code, {"Content-Type": type}); if (body && body.pipe) body.pipe(response); else response.end(body); } if (request.method in methods) methods[request.method](urlToPath(request.url), respond, request); else respond(405, "Method " + request.method + " not allowed.");}).listen(8000);说明:
1. methods 存储文件操作方法,属性名是相应的http method(GET, PUT, DELETE),属性值是对应的function。
2. 如果在methods中找不到相应的方法,则返回405.
3. pipe() 在readable stream和writable stream之间建立管道,自动把数据传送过去。
9.3. urlToPath()
function urlToPath(url) { var path = require("url").parse(url).pathname; return "." + decodeURIComponent(path);}使用内置的url模块,把url转换成 pathname。
9.4. Content-Type
server给client返回文件时,需要知道文件的类型。这需要用到mime模块,用npm安装:
$ npm install mime
9.5. GET
methods.GET = function(path, respond) { fs.stat(path, function(error, stats) { if (error && error.code == "ENOENT") respond(404, "File not found"); else if (error) respond(500, error.toString()); else if (stats.isDirectory()) fs.readdir(path, function(error, files) { if (error) respond(500, error.toString()); else respond(200, files.join("\n")); }); else respond(200, fs.createReadStream(path), require("mime").lookup(path)); });};fs.stat() 读取文件状态。fs.readdir() 读取目录下的文件列表。这段代码挺直观。
9.6. DELETE
methods.DELETE = function(path, respond) { fs.stat(path, function(error, stats) { if (error && error.code == "ENOENT") respond(204); else if (error) respond(500, error.toString()); else if (stats.isDirectory()) fs.rmdir(path, respondErrorOrNothing(respond)); else fs.unlink(path, respondErrorOrNothing(respond)); });};删除一个不存在的文件,返回 204,为什么呢? 2xx 代表成功,而不是error。
当一个文件不存在,我们可以说DELETE请求已经被满足了。而且,HTTP标准鼓励我们,多次响应一个请求,最好返回相同的结果。
function respondErrorOrNothing(respond) { return function(error) { if (error) respond(500, error.toString()); else respond(204); };}
9.7. PUT
methods.PUT = function(path, respond, request) { var outStream = fs.createWriteStream(path); outStream.on("error", function(error) { respond(500, error.toString()); }); outStream.on("finish", function() { respond(204); }); request.pipe(outStream);};这里没有检查文件是否存在。如果存在直接覆盖。又一次用到了 pipe, 把request直接连接到 file stream上。
9.8. 运行
把上面实现的server运行起来,使用curl测试它的功能:
$ curl http://localhost:8000/file.txtFile not found$ curl -X PUT -d hello http://localhost:8000/file.txt$ curl http://localhost:8000/file.txthello$ curl -X DELETE http://localhost:8000/file.txt$ curl http://localhost:8000/file.txtFile not found
10. Error Handling
如果上面的file server运行中抛出异常,会怎样? 崩溃。 需要try ... catch 捕获异常,try写在哪里呢? 所有的行为都是异步的,我们需要写好多的try,因为,每一个callback中都需要单独捕获异常,否则,异常会直接被抛到函数调用的栈顶。
写那么多的异常处理代码,本身就违背了 “异常” 的设计初衷。它的初衷是为了集中处理错误,避免错误处理代码层层嵌套。
很多node程序不怎么处理异常,因为,从某种角度来讲,出现异常就是出现了程序无法处理的错误,这时让程序崩溃是正确的反应。
另一种办法是使用Promise,它会捕获所有异常,转到错误分支。
看一个例子:
var Promise = require("promise");var fs = require("fs");var readFile = Promise.denodeify(fs.readFile);readFile("file.txt", "utf8").then(function(content) { console.log("The file contained: " + content);}, function(error) { console.log("Failed to read file: " + error);});Promise.denodeify() 把node函数Promise化 —— 还实现原来的功能,但返回一个Promise对象。
用这种方法重写 file server 的GET方法:
methods.GET = function(path) { return inspectPath(path).then(function(stats) { if (!stats) // Does not exist return {code: 404, body: "File not found"}; else if (stats.isDirectory()) return fsp.readdir(path).then(function(files) { return {code: 200, body: files.join("\n")}; }); else return {code: 200, type: require("mime").lookup(path), body: fs.createReadStream(path)}; });};function inspectPath(path) { return fsp.stat(path).then(null, function(error) { if (error.code == "ENOENT") return null; else throw error; });}
11. Exercise: Content Negotiation, Again
用http.request() 实现第17章的习题一。
var http = require("http");function readStreamAsString(stream, callback) { var data = ""; stream.on("data", function(chunk) { data += chunk.toString(); }); stream.on("end", function() { callback(null, data); }); stream.on("error", function(error) { callback(error); });}["text/plain", "text/html", "application/json"].forEach(function (type) { var req = { hostname: "eloquentjavascript.net", path: "/author", method: "GET", headers: {"Accept": type} }; var request = http.request(req, function (response) { if (response.statusCode != 200) { console.error("Request for " + type + " failed: " + response.statusMessage); } else { readStreamAsString(response, function (error, data) { if (error) throw error; console.log("Type " + type + ": " + data); }); } }); request.end();});概念都明白了,轮到自己写代码时,才发现快忘光了。 一定要打开编辑器,不看答案,手敲一遍。
12. Exercise: Fixing a Leak
function urlToPath(url) { var path = require("url").parse(url).pathname; var decoded = decodeURIComponent(path); return "." + decoded.replace(/(\/|\\)\.\.(\/|\\|$)/g, "/");}
13. Exercise: Creating Directories
methods.MKCOL = function(path, respond) { fs.stat(path, function(error, stats) { if (error && error.code == "ENOENT") fs.mkdir(path, respondErrorOrNothing(respond)); else if (error) respond(500, error.toString()); else if (stats.isDirectory()) respond(204); else respond(400, "File exists"); });};
14. Exercise: A Public Space on The Web
这道题相当复杂,稍后再看。
- Eloquent JavaScript 笔记 十九:Node.js
- 《Eloquent JavaScript》笔记--函数;
- Eloquent JavaScript 笔记 三: Functions
- Eloquent JavaScript 笔记 十: Modules
- Eloquent JavaScript 笔记 十三:DOM
- Eloquent JavaScript 笔记 十七:HTTP
- 《Eloquent JavaScript》笔记--对象与数组
- 《Eloquent JavaScript》笔记--程序的结构;
- Eloquent JavaScript 笔记 二:Program Structure
- Eloquent JavaScript 笔记 五: High-Order Functions
- Eloquent JavaScript 笔记 四:Objects and Arrays
- Eloquent JavaScript 笔记 七: Electronic Life
- Eloquent JavaScript 笔记 十一:A Programming Language
- Eloquent JavaScript 笔记 十四:Handling Event
- Eloquent JavaScript 笔记 十五:A Platform Game
- Eloquent JavaScript 笔记 十六:Drawing on Canvas
- Eloquent JavaScript 笔记 二十:略有遗憾
- node.js/javascript 语法基础笔记
- 【LeetCode】13. Roman to Integer
- 使用connect by进行级联查询
- 将avi视频转换为多帧的dicom图
- java设计模式之单例模式
- 了解自定义控件
- Eloquent JavaScript 笔记 十九:Node.js
- 自然语言处理基础(4)--数据平滑技术
- Android Service完全解析,关于服务你所需知道的一切(上)
- Collection 和 Collections的区别。
- 解析Android上强大的图表库MPAndroidChart
- Unity3D开发之委托系列
- HttpClient常用操作
- hibernate学习之多对多
- 刷清橙OJ--A1093.闰年