Node.js读书笔记
2015-10-10 21:13
567 查看
七天学会Node.js http://nqdeng.github.io/7-days-nodejs/#1.5.6 Node.js入门 http://www.nodebeginner.org/index-zh-cn.html Node.js资源汇总 https://cnodejs.org/topic/54128625351649283bcc5b30 1.Node.js -- 是一个可以让JavaScript运行在服务器端的开发平台,充分考虑了实时响应、超大规模数据要求下架构的可扩展性, 采用了单线程、异步式I/O、事件驱动的程序设计模型,带来了巨大的性能提升,减少了多线程程序设计的复杂性,提高了开发效率。 2.Node.js能做什么? Node.js为网络而生,主要用于以下领域的开发 1.具有复杂逻辑的网站 2.基于社交网络的大规模Web应用 3.Web Socket服务器 4.TCP/UDP套接字应用程序 5.命令行工具和交互式终端程序 6.待遇图形用户界面的本地应用程序 说明: Node.js内建了HTTP服务器支持,即可以轻而易举地实现一个网站和服务器的组合 3.异步式I/O与事件驱动架构设计代替多线程,提升性能 --- Node.js最大的特点 对于高并发解决方案: 传统的架构:多线程模型,为每个业务逻辑提供一个系统线程,通过系统线程切换来弥补同步式I/O调用时的时间开销 Node.js: 采用单线程模型,对于所有的I/O都采用异步式的请求方式,避免了频繁的上下文切换, Node.js在执行过程中会维护一个事件队列,程序在执行时进入事件循环等待下一个事件到来,每个异步式I/O请求完成后会被推送到事件队列,等待程序进程进行处理 Node.js的异步机制是基于事件的,所有的磁盘IO,、网络通信、数据库查询都以非阻塞方式请求,返回的结果由事件循环来处理 4.安装Node.js 1.Windows下直接下载安装包即可,会自动安装node.js和npm 2.Linux下 需要分别安装node.js和npm包管理器 sudo apt-get install node sudo apt-get install npm 5.运行Node.js程序 1.node命令进入perl命令模式,在里面执行程序 2.创建xx.js,进入到该目录,运行命令 node xx.js 运行程序 3.node -e "console.log('hello node.js')" 通过 -e参数,直接运行参数后的程序段 6.建立HTTP服务器 1.创建app.js文件 2.编写代码 var http = require('http'); http.createServer(function(req,res){ res.writeHead(200,{'Content-type':'text/html'}); res.write('<h1>Node.js</h1>'); res.end('<p>Hello World</p>'); }).listen(3000); console.log("HTTP Server is listening at port 3000"); 3.运行程序 进入app.js目录,输入命令 : node app.js 说明: 1.该程序调用了Node.js提供的http模块,对所有Http请求答复同样的内容并监听3000端口 2.浏览器中访问 http://127.0.0.1:3000 3.程序一直等待,直到Ctrl+C结束程序运行 4.listen函数中创建了事件监听器,使得node.js进程不会退出事件循环 7.使用supervisor方便代码调试 Node.js运行时,修改代码只有重启服务后才能生效,使用supervisor可以让你在修改代码后,不手动重启(supervisor帮你在代码变动时自动重启)即可看到变化 1.安装: npm install -g supervisor 2.使用supervisor命令启动app.js supervisor app.js Node的小基友supervisor 每次修改代码后会自动重启。懒程序员就指望这种省事省力的工具活着了:) 安装:npm install -g supervisor 执行:supervisor app.js 8.几组常见概念: 同步:发出一个请求调用时,在没有得到结果之前,该调用就不返回 --- 即:请求一定要得到回应才返回 异步:当异步过程调用发出后,调用者不会立即得到结果,实际处理这个调用的部件是在调用发出后,通过状态、通知来通知调用者,或通过回调函数处理这个调用 --- 即:请求发出后,继续执行其他任务,服务器处理完后,通过回调将结果发送给调用者 阻塞: 阻塞调用 --- 调用结果返回之前,当前线程会被挂起,函数只有在得到结果之后才回返回 非阻塞:非阻塞调用 --- 调用结果返回之前,函数不会阻塞当前线程,而会立刻返回 说明: 同步与异步 --- 关注的时消息通信机制 同步 --- 就是在发出一个 调用 时,在没有得到结果前,该 调用 就不返回,但一旦调用返回,就得到返回值了(执行完毕了) --- 调用者 主动等待这个 调用的结果 异步 --- 调用 发出后,这个调用就直接返回,没有返回结果 --- 当一个异步过程调用发出后,调用者不会立刻得到结果,而是在 调用 发出后,被调用者 通过状态、通知来通知调用者,或通过回调函数处理这个调用 eg: 典型的异步编程模型比如Node.js 举个通俗的例子: 你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。 而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。 阻塞与非阻塞 --- 关注的是程序在等待调用结果(消息、返回值)时的状态 阻塞调用 --- 指调用之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回 非阻塞调用 --- 指在不能立刻得到结果之前,该调用不会阻塞当前线程 eg: 你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。 在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。 9.异步式非阻塞I/O与事件式编程 Node.js最大的特点就是异步式IO与事件紧密结合的编程模式,控制流很大程度上要靠事件和回调函数来组织,一个逻辑要拆分为若干个单元 阻塞与线程: 1.阻塞: 线程在执行中如果遇到磁盘读写或网络通信等IO操作,通常要耗费很长时间,这是操作系统会剥夺这个线程的CPU控制权,使其暂停执行,同时将资源让给其他的工作线程,这种线程调度方式称为阻塞。 当I/O操作完毕时,操作系统架构这个线程的阻塞状态解除,恢复其对CPU的控制权,令其继续执行,这么IO模式称为同步式I/O或阻塞式IO 2.异步式IO或非阻塞式IO: 当线程遇到IO操作时,不会以阻塞方式等待IO操作的完成或数据的返回,而只是将IO请求发送给操作系统,继续执行下一条语句, 当操作系统完成IO操作时,以事件形式通知执行IO操作的线程,线程会在特定时候处理这个事件,为了处理异步IO,线程必须有事件循环,不断地检查有没有未处理的事件,依次予以处理 10.回调函数 eg:以异步方式读取文件为例 --- readfile.js var fs = require('fs'); fs.readFile('file.txt','utf-8',function(err,data){ if(err){ console.error(err); }else{ console.log(data); } }); 说明:这里使用了匿名回调函数,也可将匿名函数单独剥离出来 function readFileCallBack(err, data) { if (err) { console.error(err); } else { console.log(data); } } var fs = require('fs'); fs.readFile('file.txt', 'utf-8', readFileCallBack); console.log('end.'); 11.事件 Node.js所有的异步IO操作在完成时都会发送一个事件到事件队列,事件由EventEmitter对象提供。 eg: 上例中的fs.readFile和http.createServer的回调函数都是通过EventEmitter来实现的 EventEmitter的用法: 运行代码后,1秒后控制台输出了 some_event occured。 原理: event对象注册了事件some_event的一个监听器,然后我们通过setTimeout在1000ms后向event对象发送事件some_event,此时会调用some_event的监听器 var EventEmitter = require('events').EventEmitter; var event = new EventEmitter(); event.on('some_event',function(){ console.log('some_event occured'); }); setTimeout(function(){ event.emit('some_event1'); },1000); 12.Node.js的事件循环机制 Node.js程序由事件循环开始,到事件循环结束,所有的逻辑都是事件的回调函数,所以Node.js始终在事件循环中,程序入口就是事件循环第一个事件的回调函数。 事件的回调函数在执行的过程中,可能会发出IO请求或直接发射(emit)事件,执行完毕后在返回事件循环,事件循环会检查事件队列中有没有未处理的事件,直到程序结束。 Node.js的事件循环对开发中不可见,有libev库实现,libev支持多种类型的事件,在Node.js中均被EventEmitter封装,libev事件循环的每一次迭代,在Node.js中就是一次Tick 13.模块和包 模块(Module)和包(Package)是Node.js最重要的支柱,开发大规模程序通常需要把各个功能拆分、封装,然后组合起来,模块正是为了实现这种方式而诞生的 Node.js提供了require函数来调用其他模块,而且模块都是基于文件,机制简单 模块 1.模块时Node.js程序的基本组成部分,文件和模块一一对应,一个Node.js文件就是一个模块,这个文件可能是JavaScript代码、JSON或编译过的C/C++扩展 eg: var http = require('http') 其中http是Node.js的一个核心模块,其内部由C++实现,外部用JavaScript封装,通过require函数获取这个模块,然后使用其中的对象 创建及加载模块 1.创建模块 --- Node.js中一个文件就是一个模块 Node.js提供了exports和require两个函数对象 exports --- 模块公开的接口 require --- 用于从外部获取一个模块的接口,即获取模块的exports对象 eg: 1.创建一个模块 module.js var name; exports.setName = function(thyName){ name =thyName; } exports.sayHello = function(){ console.log('Hello '+name); } 2.同一目录下创建 getModule.js 调用模块 var myModule = require('./module'); myModule.setName('Jay'); myModule.sayHello(); 3.运行结果 Hello Jay 4.程序说明 module.js通过exports对象把setName和sayHello作为模块的访问接口,在getModule.js中通过require('./module')加载这个模块, 然后就可直接访问module.js中exports对象的成员函数了,npm提供了的上万个模块都是通过这种简单的方式搭建起来的 2.单次加载 --- require不会重复加载模块,即:无论调用多少次require,获得的模块都是同一个 eg: loadmodule.js var hello1 = require('./module'); hello1.setName("aa"); var hello2 = require('./module'); hello2.setName("bb"); hell01.sayHello(); 结果: Hello bb 原因: 变量hello1和hello2指向的时同一个实例,因日hello1.setName的结构被hello2.setName覆盖,最终输出结果由后者决定 3.覆盖exports 只想把一个对象封装到模块中 ---- 特别注意:通过覆盖exports方式,防止因单次加载而覆盖变量的值 eg: 1.定义hello.js模块 function Hello(){ var name; this.setName = function(thyName){ name = thyName; } this.sayHello = function(){ console.log("Hello "+name); } }; 推荐方式1: module.exports = Hello; 原始方式2: exports.Hello = Hello; 2.调用hello.js模块 方式1获取Hello模块对象: var Hello = require('./hello'); //方式2获取Hello模块对象: //var Hello = require("./hello").Hello; hello = new Hello(); hello.setName("aa"); hello.sayHello(); 特别说明: 1.模块接口的唯一变化是使用module.exports=Hello代替了exports.Hello = Hello, 外部引用该模块时,其接口对象就是要输出的Hello对象本事,而是通过原始方式输出的exports 2.事实上,exports本身仅仅是一个普通的空对象,即:{},它专门用来声明接口,本质上是通过它 为模块闭包的内部建立了一个有限的访问接口,因为它没有任何特殊的地方,所有可用其他东西来代替 14.创建包 1.包是在模块基础上更深一步的抽象,Node.js中的包类似Java中的类库,它将某个独立的功能封装起来,用于发布、更新、依赖管理和版本控制 2.包通常是一些模块的集合,在模块的基础上提供了更高层的抽象,相当于提供了一些固定接口的函数库,通过定制package.json,我们可以创建更复杂、更完善、更符合规范的包用于发布。 1.作为文件夹模块 最简单的包,就是一个作为文件夹的的模块 eg: 1.创建一个somepackage文件夹 2.在该文件夹下创建index.js exports.hello = function(){ console.log("Hello from package"); } 3.在somepackage同级目录下创建getpackage.js文件,调用somepackage包下的模块 var somePackage = require("./somepackage"); somePackage.hello(); 4.输出结果 Hello 2.package.json 1.在上例中的somepackage文件夹下,创建一个package.json文件,内容如下 { "main":"./lib/interface.js" } 2.将index.js重命名为interface.js并放到lib子文件夹下,以同样的方式调用这个包,依然可正常使用 3.说明: Node.js在调用某个包时,会首先检查包中的package.json文件的main字段,将其作为包的接口模块, 如果package.json或main字段不存在,会尝试寻找index.js或index.node作为包的接口, main字段指明了接口模块文件的位置 15.Node.js包管理器 npm 是Node.js官方提供的包管理工具,它已成为Node.js包的标准发布平台,用于Node.js包的发布、传播、依赖控制 npm 提供了命令行工具,可以方便的下载、安装、升级、删除包,也可让你作为开发者发布并维护包 1.获取一个包 格式: npm [install/i] [package_name] eg: 安装express $ npm install express 或 $ npm i express 说明: 安装信息显示完后,express就按照成功了,并放置在当前目录的node_modules子目录下, npm在获取express时还将自动解析其依赖,并获取express依赖的mime、mkdirp、qs和connect 2.本地模式和全局模式 npm安装包时两种模式: 本地模式和全局模式, 本地模式 --- 默认 即:把包安装到当前目录的node_modules子目录下,本地模式不会注册PATH环境变量 全局模式 --- npm [install/i] -g [package_name],使用全局模式安装的包不能直接在js文件中require获得,全局模式会注册PATH环境变量 对比 模式 可使用require使用 注册PATH 本地模式 是 否 全局模式 否 是 总结: 当我们是要把某个包作为工程运行时的一部分时,通过本地模式获取,如果要在命令行下使用,则使用全局模式安装 说明: 1.npm默认情况下会从http://npmjs.org搜索和下载包,将包安装到当前目录的node_modules子目录下 2.Node.js的require在加载模块时会尝试搜寻node_modules子目录,因此使用npm本地模式安装的包可直接被引用 3.创建全局链接 npm link命令 --- 在本地包和全局包之间创建符号链接,使得require可以间接使用全局模式安装的包 --- 仅支持Linux,不支持Windows eg: 1.全局模式安装express npm install -g express 2.创建全局链接 npm link express ./node_modules/express -> /usr/local/lib/node_modules/express 4.包的发布 npm init --- 根据交互式问答产生一个符合标准的package.json 16.Node.js核心模块 1.全局对象 JavaScript中的一个特殊对象,它及其所有属性都可在程序的任何地方访问,即全局变量。 浏览器JS中,window是全局底线 Node.js中的全局对象是global,所有全局变量都是global对象的属性 在Node.js中能够直接访问到对象通常都是global的属性,如:console、process等 2.全局对象与全局变量 global最根本的作用是作为全局变量的宿主 全局变量 -- 在最外层定义的变量,全局对象的属性,隐式定义的变量(未定义直接赋值的变量) Node.js中你不可能在最外层定义变量,因为所有用户代码都是属于当前模块的,而模块本身不是最外层上下文 特别注意: 永远使用var定义变量,以避免引入全局变量,因为全局变量会污染命名空间,提高代码的耦合风险 3.两个常用全局变量 process 和 console 1.process --- 是一个全局变量,即global对象的属性。它用于描述当前的Node.js进程状态,提供了一个与操作系统的简单接口 process对象的一些最常用的成员方法 process.argv --- 命令行参数数组 第一个元素是 node, 第二个元素是 脚本文件名, 第三个元素开始每个元素是一个运行参数 process.stdout --- 标准输出流,通常我们使用console.log()打印字符,而process.stdout.write()函数提供了更底层的接口 process.stdin --- 标准输入流,初始时它是暂停的,要想从标准输入流读取数据,必须回复流,并手动编写流的事件响应函数 eg: process.stdin.resume(); process.stdin.on('data',function(data){ process.stdout.write('read from console: '+data.toString()); }); process.nextTick(callback) --- 功能是为事件循环设置一项任务,Node.js会在下次事件循环响应时调用callback eg:把耗时操作拆分为两个事件,减少每个事件的执行时间,提供事件响应速度 function doSomething(args, callback){ somethingComplicated(args); process.nextTick(callback()); } doSomething(function onEnd(){ compute(); }) 2.console --- 用于提供控制台标准输出 console对象常用方法 console.log() --- 向标准输出流打印字符并以换行符结束 console.error() --- 向标准错误流输出 console.trace() --- 向标准错误流输出当前的调用栈 4.常用工具util util是一个Node.js核心模块,提供常用函数的集合 1.util.inherits --- util.inherits(constructor, superConstructor)是一个实现对象间原型继承的函数 eg: var util = require('util'); function Base(){ this.name = 'base'; this.base = 1991; this.sayHello(){ console.log("Hello "+this.name); }; } Base.prototype.showName = function(){ console.log(this.name); } function Sub(){ this.name = 'Sub'; } util.inherits(Sub,Base); var objBase = new Base(); objBase.showName(); objBase.sayHello(); console.log(objBase); var objSub = new Sub(); objSub.showName(); //objSub.sayHello(); console.log(objSub); 结果: base Hello base { name: 'base', base: 1990, sayHello: [Function] } sub { name: 'sub' } 分析: 我们定义了一个基础对象 Base 和一个继承自 Base 的 Sub,Base 有三个在构造函数 内定义的属性和一个原型中定义的函数,通过 util.inherits 实现继承。运行结果如下: base Hello base { name: 'base', base: 1991, sayHello: [Function] } sub { name: 'sub' } 注意,Sub 仅仅继承了 Base 在原型中定义的函数,而构造函数内部创造的 base 属 性和 sayHello 函数都没有被 Sub 继承。同时,在原型中定义的属性不会被 console.log 作 为对象的属性输出。如果我们去掉 objSub.sayHello(); 这行的注释,将会看到: node.js:201 throw e; // process.nextTick error, or 'error' event on first tick ^ TypeError: Object #<Sub> has no method 'sayHello' at Object.<anonymous> (/home/byvoid/utilinherits.js:29:8) at Module._compile (module.js:441:26) at Object..js (module.js:459:10) at Module.load (module.js:348:31) at Function._load (module.js:308:12) at Array.0 (module.js:479:10) at EventEmitter._tickCallback (node.js:192:40) 2.util.inspect --- util.inspect(object, [showHidden], [depth], [colors]) 是一个将任意对象转换为字符串的方法,通常用于调试和错误输出 它只是接受一个参数Object,即:要转换的对象 showHidden --- 可选参数,如果是true,建辉输出更多隐藏信息 depth --- 表示最大递归层数 color --- true用于在终端显示更漂亮效果 5.事件驱动 events --- Node.js最重要的模块,它提供了唯一接口,是Node.js事件编程的基石 1.事件发射器 events模块只提供了一个对象: events.EventEmitter EventEmitter核心就是事件发射与事件监听器功能的封装,EventEmitter的每个事件由一个事件名和若干个参数组成, 对于每个事件,EventEmitter支持若干个事件监听器,当事件发射时,注册到这个事件的事件监听器被依次调用,事件参数作为回调函数参数传递 eg: var events = require("events"); var emitter = new events.EventEmitter(); //为emitter添加事件监听 emitter.on('someEvent',function(arg1, arg2){ console.log("listener1",arg1,arg2); }); //为emitter再添加一个事件监听 emitter.on('someEvent',function(arg1, arg2){ console.log("listener2",arg1,arg2); }); //生成一个someEvent事件 emitter.emit("someEvent","Jay",1990); 结果: listener1 Jay 1990 listener2 Jay 1990 分析: emitter为事件someEvent注册了两个事件监听器,然后发射了一个someEvent事件,两个事件监听器回调函数被先后调用。 2.EventEmitter常用的API --- 特别注意 EventEmitter.on(event,listener) --- 为指定事件注册一个监听器,接受一个字符串event事件名 和 一个回调函数listener EventEmitter.emit(event,[arg1],[arg2],[...]) --- 发射名为event的事件,传递若干可选参数到事件监听器的参数表 EventEmitter.once(event,listener) --- 为指定事件注册一个单次监听器,即:监听器最多只会触发一次,触发后立刻解除该监听器 EventEmitter.removeListener(event,listener) --- 移除指定事件的某个监听器,listener必须是该事件已经注册过的监听器 EventEmitter.removeAllListeners([event]) --- 移除所有事件的所有监听器,如果指定event,则移除指定事件的所有监听器 3.error事件 遇到异常时通常会发射error事件,当error被发射时,EventEmitter规定:如果没有相应的监听器,Node.js会把它当做异常,退出程序并打印调用栈。 一般要为会发射error事件的对象设置监听器,避免遇到错误后整个程序崩溃 eg: var events = require("events"); var emitter = new events.EventEmitter(); //为error事件注册监听器 emitter.on("error",function(){ console.log("程序出现了error"); }) //发射error事件 emitter.emit("error"); 4.继承EventEmitter --- 大部分时候不会直接使用EventEmitter,而是在对象中继承它,包括:fs、net、http在内的,只要是支持事件响应的核心模块都是EventEmitter的子类 6.文件系统 fs --- fs模块时文件操作的封装,它提供了文件的读取、写入、更名、删除、遍历目录、链接等文件系统操作 fs模块中所有的操作都提供了异步和同步的两个版本 eg: fs.readFile() --- 异步读取文件内容 fs.readFileSync() --- 同步读取文件内容 1.fs.readFile(filename,[encoding],[callback(err,data)]) --- 异步读取文件 err --- 表示有没有错误发生,如果有错误发送,err将会是Error对象 data --- 文件内容,如果指定了encoding,data是一个解析后的字符串,否则data将会是以Buffer形式表示的二进制数据 eg: var fs = require("fs"); fs.readFile("context.txt","utf-8",function(err,data)){ if(err){ console.log(err); }else{ console.log(data); } } 2.fs.open(path,flags,[model],[callback(err,fd)]) 与 C 语言标准库中的 fopen 函数类似。它接受两个必选参数,path 为文件的路径, flags 可以是以下值。 r :以读取模式打开文件。 r+ :以读写模式打开文件。 w :以写入模式打开文件,如果文件不存在则创建。 w+ :以读写模式打开文件,如果文件不存在则创建。 a :以追加模式打开文件,如果文件不存在则创建。 a+ :以读取追加模式打开文件,如果文件不存在则创建。 mode 参数用于创建文件时给文件指定权限,默认是 0666①。回调函数将会传递一个文件描述符 fd②。 3.fs.read(fs,buffer,offset,length,position,[callback(err,bytesRead,buffer)]) --- 从指定的文件描述符fs中读取数据并写入buffer指向的缓冲区对象 offset --- 是buffer的写入偏移量 length --- 是要从文件中读取的字节数 position --- 是文件读取的起始位置,如果position未null,则会从当前文件指针的位置读取 callback --- 回调函数传递bytesRead和buffer,分布表示读取的字节数和缓冲区对象 eg: var fs = require("fs"); fs.open("content.txt",'r',function(err,fd)){ if(err){ console.error(err); return; } var buf = new Buffer(8); fs.read(fd,buf,0,8,null,function(err,bytesRead,buffer){ if(err){ console.error(err); return; } console.log("bytesRead: "+bytesRead); console.log(buffer); }); } 7.HTTP服务器与客户端 Node.js标准库提供了http模块,其中封装了一个高效的HTTP服务器和一个简易的HTTP客户端 http.Server是一个基于事件的HTTP服务器,它的核心由Node.js下层C++部分实现,而接口由JavaScript封装,兼顾了高性能与简易性 http.request是一个HTTP客户端工具,用于向HTTP服务器发起请求 1.HTTP服务器 http.Server是http模块中的HTTP服务器对象,用Node.js做的所有基于HTTP协议的系统,eg:网站、社交应用甚至服务器,都是基于http.Server实现的。 它提供了一套封装级别很低的API,仅仅是流控制和简单的消息解析,所有的高层功能都要通过它的接口来实现。 eg:使用http实现一个服务器,通过 127.0.0.1:30000来访问 var http = require("http"); http.createServer(function(req,res){ res.writeHead(200, {'Content-Type': 'text/html'}); res.write('<h1>Node.js</h1>'); res.end('<p>Hello World</p>'); }).listener(30000); console.log("Http Server is listening at port 30000"); 解析: http.createServer创建了一个http.Server实例,将一个函数作为HTTP请求处理函数。 函数的两个参数: req --- 请求对象 res --- 响应对象 1.http.Server事件 http.Server是一个基于事件的HTTP服务器,所有的请求都被封装为独立的事件,开发者只需对它的事件编写响应函数即可实现HTTP服务器的所有功能, 它继承自EventEmitter,提供了以下几个事件 request: 当客户端请求到来时,该事件被触发,提供两个参数req和res,分别是http.ServerRequest和http.ServerResponse的实例,表示请求和响应信息 connection : 当TCP连接建立时,该事件被触发,提供一个参数socket,为net.Socket的实例 connection事件的粒度要大于request,因为客户端在Keep-Alive模式下可能会在同一个连接内发送多次请求 close: 当服务器关闭时,该事件被触发。忒别注意:不是在用户连接断开时 2.requets: 最常用的事件 : http提供了 http.createServer([requestListener]) --- 创建一个HTTP服务器并将requestListener作为request事件的监听函数 eg: var http = require("http"); var server = new http.Server(); server.on("request",function(req,res)){ ...... } server.listen(8888); <==等价于==> var http = require("http"); http.createServer(function(rep,res){ ...... }).listen(8888); 3.http.ServerRequest 1.http.ServerRequest是HTTP请求的信息,是后端开发者最关注的内容,它一般由http.Server的request事件发送,作为第一个参数传递 2.ServerRequest提供的一些属性: HTTP请求分为两部分:请求头(Request Header)和请求体(Request Body). 请求体长度较长,需要一定的时间传输,因此 http.ServerRequest提供了3个事件用于控制请求体传输 data --- 当请求体数据到来时,该事件被触发。该事件提供一个参数chuk,表示接收到的数据。 如果该事件没有被监听,那么请求体将会被抛弃,该事件可能会被调用多次。 end --- 当请求体数据传输完成时,该事件被触发,伺候将不会再有数据到来 close --- 用户当前请求结束时,该事件被触发。不同于end,如果用户强制终止了传输,也还是调用close ServerRequest 的属性 名 称 含 义 complete 客户端请求是否已经发送完成 httpVersion HTTP 协议版本,通常是 1.0 或 1.1 method HTTP 请求方法,如 GET、POST、PUT、DELETE 等 url 原始的请求路径,例如 /static/image/x.jpg 或 /user?name=byvoid headers HTTP 请求头 trailers HTTP 请求尾(不常见) connection 当前 HTTP 连接套接字,为 net.Socket 的实例 socket connection 属性的别名 client client 属性的别名 4.获取GET请求内容 Node.js的url模块中的parse函数,提供了获取GET请求内容的功能 说明: 由于GET请求直接被嵌入到路径中,URL是完整的请求路径,包括了?后面的部分,因此可手动解析URL中的参数,或通过url模块解析请求参数 eg:解析URL的GET路径和参数 httpserverrequestget.js //1.引入所需模块 var http = require("http"); var url = require("url"); var util = require("util"); //2.创建HTTP服务器,并通过url默解析URL和参数 http.createServer(function(req, res){ req.writeHead(200,{"Content-type":"text/plain"}); //3.将解析后的URL参数输出到网页 req.end(util.inspect(url.parse(req.url,true))); }) 结果:http://192.168.1.246:8888/user?name=byvoid&email=byvoid@byvoid.com { protocol: null, slashes: null, auth: null, host: null, port: null, hostname: null, hash: null, search: '?name=byvoid&email=byvoid@byvoid.com', query: { name: 'byvoid', email: 'byvoid@byvoid.com' }, pathname: '/user', path: '/user?name=byvoid&email=byvoid@byvoid.com', href: '/user?name=byvoid&email=byvoid@byvoid.com' } 分析: 通过url.parse,原始的path被解析为一个对象,其中 query --- 对应GET请求的参数内容 path --- 表示请求路径 5.获取POST请求内容 --- POST请求的内容全部在请求体中 http.ServerRequest 并没有一个属性内容为请求体,原因是等待请求体传输可能是一件耗时的工作, 譬如上传文件。而很多时候我们可能并不需要理会请求体的内容,恶意的 POST请求会大大消耗服务器的资源。 所以 Node.js 默认是不会解析请求体的,当你需要的时候,需要手动来做 eg:一个简单的获取POST参数的例子,仅做演示:不能在生产环境中使用!!! var http = require("http"); var querystring = require("querystring"); var util = require("util"); http.createServer(function(req, res){ var post = ""; req.on("data",function(chunk){ post += chunk; }); req.on("end",function(){ post = querystring.parse(post); res.end(util.inspect(post)); }); }).listen(8888); 特别说明: 在node js中常用的web server是express。 在express中,可以通过req.param('key')来获取post回来的参数。 6.http.ServerResponse 这是返回个客户端的信息,决定了用户最终能看到的结果,它由http.Server的request事件发送,作为第二个参数传递 3个主要函数: response.writeHead(statusCode,[headers]) --- 向请求的客户端发送响应头,该函数在一个请求中最多调用一次,若不调用,则自动生成一个响应头 statusCode : HTTP状态码 eg: 200,400(未找到) headers : 响应头的属性 response.write(data,[encoding]) --- 向请求的客户端发送响应内容,在response.end调用之前,response.write可调用多次 data : 一个Buffer或字符串,表示要发送的内容,如果data是字符串,需要制定encoding来说明它的编码方式,默认是 utf-8 说明:也可发送二进制文件 eg: response.writeHead(200,{"Content-type":"image/jpeg"}); response.write(file,"binary"); response.end([data],[encoding]) --- 结束响应,告知客户端所有发送已完成 当所有要返回的内容发送完毕的时候,该函数 必须 被调用一次。它接受两个可选参数, 意义和 response.write 相同。如果不调用该函数,客户端将永远处于等待状态。 7.HTTP客户端 http模块提供了http.request和http.get两个函数,作为客户端向HTTP服务器发送请求 http.request和http.get函数 -- 都返回一个http.ClientRequest的实例 1. http.request(options, callback) --- 发起HTTP请求 options 是一个类似关联数组的对象,表示请求的参数,常用参数如下 host:请求网站的域名或IP地址 port:请求网站的端口,默认:80 method:请求方法,默认GET path :请求相对于根的路径,默认 / ,GET请求时包括查询参数QueryString eg: /search?query=jay headers :请求头内容 eg:下面是一个通过 http.request 发送 POST 请求的代码: //httprequest.js var http = require('http'); var querystring = require('querystring'); var contents = querystring.stringify({ name: 'byvoid', email: 'byvoid@byvoid.com', address: 'Zijing 2#, Tsinghua University', }); var options = { host: 'www.byvoid.com', path: '/application/node/post.php', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length' : contents.length } }; var req = http.request(options, function(res) { res.setEncoding('utf8'); res.on('data', function (data) { console.log(data); }); }); req.write(contents); req.end(); 运行后结果如下: array(3) { ["name"]=> string(6) "byvoid" ["email"]=> string(17) "byvoid@byvoid.com" ["address"]=> string(30) "Zijing 2#, Tsinghua University" } callback 是回调函数,传递一个http.ClientResponse实例的参数 2. http.get(options, callback) --- http.request的简化版,自动将请求方法设置为了GET请求,同时不需要手动调用req.end() eg: var http = require("http"); var req = http.get({host:"www.baidu.com"}); req.on("data",function(res){ res.setEncoding("utf8"); res.on("data",function(data){ console.log(data); }); }); 两个对象: http.ClientRequest --- 由http.request或http.get返回产生的对象,表示一个已经产生而且正在进行中的HTTP请求。 它提供了一个response事件,即http.request或http.get第二个参数指定的回调函数的绑定对象参数。 http.ClientResponse --- 提供了三个事件data、end、close,分别在数据到达、传输结束和连接结束时触发。 data事件传递一个参数chunk,表示接收到的数据 常见属性: statusCode HTTP状态码,200,、400、500 httpVersion HTTP协议版本 1.0或1.1 headers HTTP请求头 常见函数: response.setEncoding([encoding]) -- 设置默认编码,当data事件被触发时,数据会以encoding编码,默认是null,不编码,以Buffer形式存储 response.pause() -- 暂停接收数据和发送事件,方便下载功能 response.resume() -- 从暂停的状态中恢复 8.Linux下完整版利用formidable上传图片并显示 ---- windows下只需要改变requestHandlers.js中的相关存取路径即可 index.js --- 程序入口 route.js --- url路由规则模块 server --- 服务器模块 requestHandlers.js --- 不同url的响应处理模块 1.index.js //程序入口,调用其他3个模块 var server = require("./server"); router = require("./route"); requestHandlers = require("./requestHandlers"); //定义handle数组,即各个url请求索要启动的模块函数方法 var handle = {}; handle["/"] = requestHandlers.start; handle["/start"] = requestHandlers.start; handle["/upload"] = requestHandlers.upload; handle["/show"] = requestHandlers.show; //启动服务器 server.start(router.route,handle); 2.route.js //在路由模块中定义url访问规则 function route(handle,pathname,response,request){ console.log("About to route a request for "+pathname); if(typeof handle[pathname]=="function"){ handle[pathname](response,request); }else{ console.log("No request handler found for "+pathname); response.writeHead(404,{"Content-type":"text/html"}); response.write("404 not found"); response.end(); } } //导出route模块 exports.route = route; 3.server.js //引入生成页面需要的http和url模块 var http = require("http"); url = require("url"); function start(route,handle){ function onRequest(request,response){ //通过url模块提供的方法,获取url路径 var pathname = url.parse(request.url).pathname; console.log("Request for "+pathname+" received"); //调用路由模块制定的访问规则 route(handle,pathname,response,request); } //创建并启动服务器 http.createServer(onRequest).listen(8888); console.log("Server has started"); } //导出模块 exports.start = start; 4.requestHanlder.js //定义每个request请求的处理,即不同url请求返回不同的页面 var querystring = require("querystring"); fs = require("fs"); formidable = require("formidable"); //start请求对应的响应 function start(response){ console.log("Request handler start was called"); //定义请求响应的的页面 var body = '<html>'+ '<head>'+ '<meta http-equiv="Content-Type" content="text/html; '+ 'charset=UTF-8" />'+ '</head>'+ '<body>'+ '<form action="/upload" enctype="multipart/form-data" '+ 'method="post">'+ '<input type="file" name="upload" multiple="multiple">'+ '<input type="submit" value="Upload file" />'+ '</form>'+ '</body>'+ '</html>'; response.writeHead(200, {"Content-Type": "text/html"}); response.write(body); response.end(); } //upload请求对应的响应 --- 接收form表单上传的图片 function upload(response,request){ console.log("Request handler upload was called"); //调用formidable对form表单进行解析和处理 var form = formidable.IncomingForm(); //定义上传路径 form.uploadDir = "/home/develop/tmp/node/test"; //处理表单内容 form.parse(request,function(error,fields,files){ console.log("about to parse form "); console.log(files.upload.path); fs.renameSync(files.upload.path,"/home/develop/tmp/node/test/test.jpg"); response.writeHead(200, {"Content-Type": "text/html"}); response.write("received image:<br/>"); response.write("<img src='/show' />"); response.end(); }); } //图片显示的请求 --- 将上传的图片显示到网页 function show(response){ console.log("Request handler show was called"); fs.readFile("/home/develop/tmp/node/test/test.jpg","binary",function(error,file){ if(error){ response.writeHead(500,{"Content-type":"text/plain"}); resonse.write(error+"\n"); response.end(); }else{ //如果没有异常,就将图片写到网页 response.writeHead(200,{"Content-type":"image/jpeg"}); response.write(file,"binary"); response.end(); } }); } //导出模块,供其他模块使用 exports.start = start; exports.upload = upload; exports.show = show; 5.访问 http://192.168.1.246:8888 17.使用Node.js进行Web开发 --- 使用Node.js实现一个微博系统 功能: 路由控制、页面模板、数据库访问、用户注册、登录、用户会话等内容 技术: Express框架、MVC设计模式、ejs或jade模板引擎、MongoDB数据库操作 MVC架构: 由模板引擎生成HTML页面,由控制器定义路径解析,由对象关系模型处理数据访问 1.使用http模块 Node.js提供了http模块,这是一个HTTP服务器内核的封装,你可用它做任何HTTP服务器能做的事,甚至实现一个HTTP代理服务器 eg:获取表单post的数据title和text var http = require("http"); var querystring = require("querystring"); var server = http.createServer(function(req,res){ var post = ""; req.on("data",function(chunk){ post +=chunk; }); req.on("end",function(){ post = querystring.parse(post); res.write(post.title); res.write(post.text); res.end(); }); }).listen(3000); 2.Express框架 --- 最稳定和广泛的,Node.js官方推荐的Web开发框架 提供的功能 --- 多数功能只是对HTTP协议中常用操作的封装,更多的功能需要插件或整合其他模块来完成 路由控制 模板解析支持 动态视图 用户会话 CSRF保护 静态文件服务 错误控制器 访问日志 缓存 插件支持 eg:实现上面用http模块实现的功能 var express = require("express"); var app = express.createServer(); app.use(express.bodyParse); app.all("/",function(req,res){ res.send(req.body.title+req.body.text); }); app.listen(3000); 解析: 不用手动编写req的事件监听器,只需加载express.bodyParse()就能直接通过req.body获取Post的数据了 3.快速开发 1.全局方式安装express npm install -g express npm install -g express-generator 查看node版本 node -v 查看express版本 express -V 2.使用express命令创建工程 --- 创建一个express+ejs项目 express -e microblog 说明: express -e project_name --- 使用ejs模块引擎创建express项目 express -J project_name --- 使用jade模块引擎创建express项目 express的版本不是通常的 “-v” 来查看,而是 “-V” 3.进入创建的项目 microblog,运行 npm install 4.启动服务器 --- 进入microblog项目,运行命令 npm start 5.访问项目 IP:3000 6.Express项目结构: 1.aap.js -- 项目入口 2.node_modules -- 存放项目的依赖库 3.package.json -- 项目依赖配置及开发者信息 4.public -- 静态文件 css,js,img等 5.routes -- 路由文件,类似MVC中的Controller控制器 6.views -- 页面文件(ejs或jade的模板,默认是:jade) 7.常用文件解析 1.routes/index.js --- 路由文件,相当于控制器,用于组织展示的内容。 var express = require('express'); var router = express.Router(); /* GET home page. */ router.get('/', function(req, res, next) { res.render('index', { title: 'Express' }); }); module.exports = router; 说明: router.get('/',function(...)) 将 / 路径映射到function函数下, res.render('index',{title:'Express'}) 调用模板解析引擎,解析名为index的模板, 并传入一个对象作为参数,这个对象只有一个属性,即 title:'Express' 2.views/index.ejs --- 模板文件,即 routes/index.js中调用的模板,用于显示res.render第二个参数传入的对象的属性 <h1><%= title %></h1> <p>Welcome to <%= title %></p> 4.路由控制 --- 网站架构最核心的部分,即 MVC架构中的控制器 工作原理: 访问 http://localhost:3000,浏览器会向服务器发送以下请求: GET / HTTP/1.1 Host: localhost:3000 Connection: keep-alive Cache-Control: max-age=0 User-Agent: Mozilla/5.0 AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.142 Safari/535.19 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip,deflate,sdch Accept-Language: zh;q=0.8,en-US;q=0.6,en;q=0.4 Accept-Charset: UTF-8,*;q=0.5 其中第一行是请求的方法、路径和 HTTP 协议版本,后面若干行是 HTTP 请求头。 1.请求网页动态数据 app.js 中 app.use('/', routes);调用routes/index.js模块, 在routes/index.js模块中,router.get('/',function(...)) 作用是 对 / 路径的GET请求由function进行处理, 通过res.render('index',{title:'Express'}) 调用模板解析引擎,解析名为index的模板,传递title变量,最终视图模板生成HTML页面,返回给浏览器 2.请求静态数据 浏览器接收到内容后,分析发现需要获取/stylesheets/style.css,因此会再次向服务器发起请求。 app.js通过app.use(express.static(path.join(__dirname, 'public'))); --- 配置了静态文件服务器, 因此/stylesheets/style.css会定向到app.js所咋目录的子目录中的文件public/stylesheets/style.css,返回给浏览器 Express创建的网站架构图: --------------------------------- | 浏览器 | --------------------------------- | | --------------------------------- | 路由控制器 | --------------------------------- | | | | | | ---------- ----------- ------------ 模板引擎 静态文件 对象模型 ---------- ----------- ------------ 典型的MVC架构,浏览器发起请求,由路由控制接受,根据不同的路径定向到不同控制器。 控制器处理用户具体请求,可能会访问数据库中对象(对象模型),控制器还有访问模板引擎, 生成视图的HTML,最后再由控制器返回给浏览器,完成一次请求。 路由规则: 在routes/index.js中添加相应的url处理的路由规则 eg:添加/hello的处理 --- 向页面输出当前时间 var express = require('express'); var router = express.Router(); /* GET home page. */ router.get('/', function(req, res, next) { res.render('index', { title: 'Express' }); }); /*GET hello page*/ router.get('/hello',function(req, res){ res.send("The time is "+new Date().toString()); }); module.exports = router; 路径匹配: Express支持更高级的路径匹配模式 --- 支持JavaScript正则表达式 eg: 想要展示一个用户的个人页面,路径为 /user/[username] 路由规则: router.get('/user/:username', function(req,res){ //向请求页面输出信息 res.send("user: "+req.params.username); }); REST风格的路由规则: REST --- 表征状态转移,它是一种基于HTTP协议的网络应用的接口风格,充分利用HTTP的方法实现统一风格接口的服务 常用的4个HTTP方式: GET : 获取 POST : 新增 PUT : 更新 DELETE : 删除 Express对每种HTTP请求方法都设计了不同的路由绑定函数: 方式 绑定函数 GET router.get(path,callback) POST router.post(path,callback) PUT router.put(path,callback) DELETE router.delete(path,callback) 所有方式 router.all(path,callback) --- 支持把所有的请求方式绑定到同一个响应函数 控制权转移: Express支持同一路径绑定多个路由响应函数,提供了路由控制权转移方法,即:回调函数的第三个参数 next, 通过调用next(),会将路由控制权转移给后面的规则 --- 类似 J2EE中的过滤器放行功能 eg:router/index.js中 router.all("/user/:username",function(req, res, next){ console.log("all methods captured"); //该路由规则处理完后,将路由控制权交给下一个路由规则 --- 放行 next(); }); router.get("/user/:username",function(req, res){ res.send("user: "+req.params.username); }); 解析: 同一个/user/:username绑定了两个路由响应函数,通过all的第三个参数 next, 处理完后,通过next()方法 将控制权转移类下一个路由规则,get方式才能处理该url 注意: get方法处理完后没有转移控制权(放行),即:后面如果还有其他的绑定函数,对/user/:username将不能处理 next控制权转移是一个非常有用的工具,可以让我轻易实现中间件,而且还能提高代码的复用程度 eg: 针对一个用户查询信息和修改信息的操作,分别对应了GET和PUT操作,而两者公有的步骤是检查用户是否存在,可用next()方法实现 var users = { "jay":{ name:"Jay", website:"http://www.feicool.com" } } router.all("/user/:username", function(req, res, next){ //检查用户是否存在 if(users[req.params.username]){ //存在则交给下一个路由规则处理 next(); }else{ //不存在则抛出异常 next(new Error(req.params.username+" does not exist.")); } }); //展示用户信息 router.get("/user/:username",function(req, res){ res.send(JSON.stringify(users[req.params.username])); }); //修改用户信息 router.put("/user/:username", function(req, res){ res.send("Done !!!"); }); 解析: router.all 这个路由规则起到了中间件的作用,把相似请求的相同部分提取出来,有利于代码维护,降低了代码耦合度 5.模板引擎 --- MVC架构中的视图,将页面模板和要显示的数据结合起来生成HTML页面,由服务器端解析成HTML后传递给客户端。 1.在Node.js的MVC架构中,模板引擎包含在服务器端,控制器得到用户请求后,从模型获取数据,调用模板引擎, 模板引擎数据和页面模板为输入,生成HTML页面,然后返回给控制器,由控制器交回客户端。 ------------ ----------- 页面模板 数据 ----------- | | ------------ ----------- | | | | | | ---------------------------- | | 模板引擎 | 控制器 | ---------------------------- | | | | | ---------------------------- | | HTML页面 ----------- | | ---------------------------- 模板引擎在MVC架构中的位置 2.使用模板引擎 ejs和jade 1.在app.js中通过以下语句设置了模板引擎和页面模板位置 app.set('views',__dirname+'/views'); app.set('view engine', 'ejs'); 说明: 要使用的模板引擎是ejs,页面模板在views子目录下 2.在routes/index.js的exports.index函数中调用模板引擎,并传递参数对象 res.render('index',{title:'Express'}) 说明: res.render --- 功能是调用模板引擎,并将其产生的页面直接返回给客户端。 两个参数: 参数一:模板名称,即views目录下的模板文件名,不包含扩展名 参数二:传递给模板的数据对象,json格式,用于模板翻译 3.ejs只有3中标签 <% code %> : JavaScript代码 <%= code %> : 显示替换过HTML特殊字符的内容 <%- code %> : 显示原始HTML内容 3.页面布局 layout.ejs页面布局模板,它描述了整个页面的框架结构,默认情况下每个单独的页面都继承自这个框架。替换掉<%- body %>这个部分 这个功能通常非常有用,因为一般为了保持整个网站的一致风格,HTML 页面的<head>部分以及页眉页脚中的大量内容是重复的, 因此我们可以把它们放在 layout.ejs 中 关闭这个功能,可在app.js中的app.configure中添加一下内容: app.set('view options',{ layout:false }) 另一种情况是,一个网站可能需要不止一种页面布局,例如网站分前台展示和后台管理 系统,两者的页面结构有很大的区别,一套页面布局不能满足需求。这时我们可以在页面模 板翻译时指定页面布局,即设置 layout 属性,例如: function(req, res) { res.render('userlist', { title: '用户列表后台管理系统', layout: 'admin' }); }; 这段代码会在翻译 userlist 页面模板时套用 admin.ejs 作为页面布局。 4.片段视图 --- partials 它就是一个页面的片段,通常是重复的内容,用于迭代显示。 通过它你可以将相对独立的页面块分割出去,而且可以避免显式地使用 for 循环。 eg:在 app.js 中新增以下内容: app.get('/list', function(req, res) { res.render('list', { title: 'List', items: [1991, 'byvoid', 'express', 'Node.js'] }); }); 在 views 目录下新建 list.ejs,内容是: <ul><%- partial('listitem', items) %></ul> 同时新建 listitem.ejs,内容是: <li><%= listitem %></li> 访问 http://localhost:3000/list,可以在源代码中看到以下内容: <!DOCTYPE html> <html> <head> <title>List</title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <ul><li>1991</li><li>byvoid</li><li>express</li><li>Node.js</li></ul> </body> </html> 解析: partial是一个可在视图中使用的函数,接受两个参数 参数一:片段视图名称 参数二:对象或数组 如果是对象,则片段视图中上下文变量引用的就是这个对象 如果是数组,则其中每个元素依次迭代应用到片段视图 片段视图中的上下文变量就是视图文件名 ,上例中的 'listitem' 6.建立微博网站 --- 初级完整项目:参考附件 功能分析: 首先,微博应该以用户为中心,因此需要有用户的注册和登录功能。微博网站最核心的功能是信息的发表,这个功能涉及许多 方面,包括数据库访问、前端显示等。一个完整的微博系统应该支持信息的评论、转发、圈点用户等功能, 但出于演示目的,我们不能一一实现所有功能,只是实现一个微博社交网站的雏形. 路由规划: 根据功能设计划分路由url规则 /:首页 /u/[user]:用户的主页 /post:发表信息 /reg:用户注册 /login:用户登录 /logout:用户登出 根据用户状态细分 1.发表信息以及用户登出页面必须是已登录用户才能操作的功能。 2.用户注册和用户登入所面向的对象必须是未登入的用户。 3.首页和用户主页则针对已登入和未登入的用户显示不同的内容。 打开 routes/index.js添加路由规则 界面设计: 1. 从http://twitter.github.com/bootstrap/下载bootstrap.zip 2. 其中所有的 JavaScript 和 CSS 文件都提供了开发版和产品版,前者是原始的代码,后者经过压缩, 文件名中带有 min。将 img 目录复制到工程 public 目录下,将 bootstrap.css、bootstrap-responsive.css 复制到 public/stylesheets 中,将 bootstrap.js 复制到 public/javascripts 目录中, 3. 然后从http://jquery.com/下载一份最新版的 jquery.js 也放入 public/javascripts 目录中。
相关文章推荐
- 启动datanode后jps下无datanode的解决方法
- node-lessons
- nodejs API笔记
- nodejs基于art-template模板引擎生成
- hadoop2.2重新格式化namenode
- node.js实践第二天
- LeetCode Remove Nth Node From End of List
- Node.js学习技术栈
- Windows环境下的Nodejs+npm+bower安装步骤
- Nodejs中常用加密方式使用实例
- NodeJS、NPM安装配置步骤(windows版本)
- 解决windows 下 node-webkit 不能播放声音的问题
- 为什么我要用 Node.js? 案例逐一介绍
- Hadoop总结一:基础知识-Namenode,Datanode(非HA高可用性配置)
- node lesson6
- nodeclub
- 解决hadoop namenode 无法启动
- Node.js 中的copy
- node lesson5--async
- Node出错导致运行崩溃的解决方案