//app.js
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.");
var util = require('util');
function Base() {
this.name = 'base';
this.base = 1991;
this.sayHello = function() {
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);
注意,Sub 仅仅继承了 Base 在原型中定义的函数,而构造函数内部创造的 base 属性和 sayHello 函数都没有被 Sub 继承。同时,在原型中定义的属性不会被 console.log 作 为对象的属性输出。如果我们去掉 objSub.sayHello(); 这行的注释,将会看到
Object #<Sub> has no method 'sayHello'
//app.js
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');
//httpserver.js
var http = require('http');
var server = new http.Server();
server.on('request', function(req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<h1>Node.js</h1>');
res.end('<p>Hello World</p>');
});
server.listen(3000);
console.log("HTTP server is listening at port 3000.");
//httpserverrequestget.js
var http = require('http');
var url = require('url');
var util = require('util');
http.createServer(function(req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'}); res.end(util.inspect(url.parse(req.url, true)));
}).listen(3000);
4. 获取POST请求内容
POST 请求的内容全部都在请求体中。 http.ServerRequest并没有一个属性内容为请求体,原因是等待请求体传输可能是一件 耗时的工作,譬如上传文件。而很多时候我们可能并不需要理会请求体的内容,恶意的 POST 请求会大大消耗服务器的资源。所以Node.js 默认是不会解析请求体的,当你需要的时候, 需要手动来做。
一个Demo
//httpserverrequestpost.js
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(3000);
上面代码并没有在请求响应函数中向客户端返回信息,而是定义了一个 post 变量,用 于在闭包中暂存请求体的信息。通过 req 的 data 事件监听函数,每当接受到请求体的数据, 就累加到 post 变量中。在 end 事件触发后,通过 querystring.parse 将 post 解析为 真正的 POST 请求格式,然后向客户端返回。
《Node js 开发指南》学习
标签(空格分隔):Node.js
一些基础的东西
ECMAScript
利用supervisor来监听代码改动时自动重启Node
sudo npm install -g supervisor
supervisor
的安装需要管理员权限,在项目中我们用的是pm2
这个工具。supervisor app.js
异步与同步的一个对比
事件
Node.js 的事件循环机制: Node.js 程序由事件循环开始,到事件循 环结束,所有的逻辑都是事件的回调函数,所以 Node.js 始终在事件循环中,程序入口就是 事件循环第一个事件的回调函数。事件的回调函数在执行的过程中,可能会发出 I/O 请求或 直接发射(emit)事件,执行完毕后再返回事件循环,事件循环会检查事件队列中有没有未 处理的事件,直到程序结束。
模块和包
注意: require 不会重复加载模块,也就是说无论调用多少次 require,获得的模块都是同一个
此时我们在其他文件中需要通过
require('./singleobject').Hello
来获取Hello 对象,这略显冗余,可以用下面方法稍微简化:这样就可以直接获得这个对象:
Node.js 的包是一个目录,其中包含一个 JSON 格式的包说明文件 package.json。严格符合 CommonJS 规范的包应该具备以下特征:
Node.js 对包的要求并没有这么严格,只要顶层目录下有package.json,并符合一些规范即可。当然为了提高兼容性,我们还是建议你在制作包的时候,严格遵守 CommonJS 规范。
package.json
Node.js 在调用某个包时,会首先检查包中 package.json 文件的 main 字段,将其作为包的接口模块,如果 package.json 或 main 字段不存在,会尝试寻找 index.js 或 index.node 作 为包的接口。
包管理器
npm
是 Node.js 官方提供的包管理工具。 包安装的两种模式:本地模式和全局模式 本地模式: 默认情况下我们使用npm install
命令就是采用本地模式,即把包安装到当前目录的node_modules
子目录下。Node.js的require
在加载模块时会尝试搜寻node_modules
子目录, 因此使用npm本地模式安装 的包可以直接被引用。 全局模式: 当我们使用全局模式安装npm install -g
时, npm 会将包安装到系统目录,譬如/usr/local/lib/node_modules/
,同时 package.json 文 件中 bin 字段包含的文件会被链接到/usr/local/bin/
。/usr/local/bin/
是在PATH 环境变量中默认定义的。使用全局模式安装的包并不能直接在 JavaScript 文件中用 require 获 得,因为 require 不会搜索
/usr/local/lib/node_modules/
.总而言之,当我们要把某个包作为工程运行时的一部分时,通过本地模式获取,如果要 在命令行下使用,则使用全局模式安装。
调试
通常只用
node-inspector
Node.js核心模块
全局对象
全局对象和全局变量
JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。在浏览器 JavaScript 中,通常 window 是全局对象, 而 Node.js 中的全局对象是
global
, 所有全局变量(除了global
本身以外)都是global
对象的属性。我们在 Node.js 中能够直接访问到对象通常都是
global
的属性,如console
、process
等.process
process 是一个全局变量,即 global 对象的属性。它用于描述当前 Node.js 进程状态 的对象,提供了一个与操作系统的简单接口。通常在你写本地命令行程序的时候,少不了要和它打交道。
process.argv 是命令行参数数组,第一个元素是 node,第二个元素是脚本文件名, 从第三个元素开始每个元素是一个运行参数
process.stdout 和 process.stdin标准输出输入流
process.nextTick(callback))的功能是为事件循环设置一项任务,Node.js 会在 下次事件循环响应时调用 callback
process.platform、 process.pid、process.execPath、process.memoryUsage()
等方法。console
常用工具
util.inherits
util.inherits(constructor, superConstructor)
实现对象间原型继承的函数 如util.inspect(object,[showHidden],[depth],[colors])
是一个将任意对象转换 为字符串的方法,通常用于调试和错误输出。Demo:
EventEmitter常用API:
EventEmitter.on(event, listener)
为指定事件注册一个监听器,接受一个字符串event和一个回调函数listener。EventEmitter.emit(event, [arg1], [arg2], [....])
发射事件,传递参数到事件监听器的参数表。EventEmitter.once(event, listener)
为指定事件注册一个单次监听器,即监听器最多只会触发一次,触发后立刻解除该监听器。EventEmitter.removeListener(event, listener)
移除指定事件的某个监听器,listener必须是该事件已经注册过的监听器。EventEmitter.removeAllListeners([event])
移除所有事件的所有监听器, 如果指定 event,则移除指定事件的所有监听器。error事件
如:
继承 EventEmitter
注意理解上面一段话。
文件系统 fs
与其他模块不同的是,fs 模块中所有的操作都提供了异步的和 同步的两个版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。
fs.readFile
fs.readFile(filename,[encoding],[callback(err,data)])
需要注意的是如果指定了encoding
, 回调中的data是一个解析后的字符串,否则data将会以Buffer形式表示的二进制数据。fs.readFileSync
Node.js 标准库提供了 http 模块,其中封装了一个高效的 HTTP 服务器和一个简易的 HTTP 客户端。
HTTP 服务器
http.Server 是 http 模块中的 HTTP 服务器对象,用 Node.js 做的所有基于 HTTP 协议的系统, 如网站、社交应用甚至代理服务器, 都是基于 http.Server实现的。它提供了一套封装级别很低的API,仅仅是流控制和简单的消息解析,所有的高层功能都要通过它的接口来实现。 一个Demo:
1 http.Server的事件 http.Server 是一个基于事件的 HTTP 服务器, 所有的请求都被封装为独立的事件,开发者只需要对它的事件编写响应函数即可实现 HTTP 服务器的所有功能。它继承自EventEmitter,提供了以下几个事件:
request
: 当客户端请求到来时,该事件被触发,提供两个参数 req 和res,分别是http.ServerRequest
和http.ServerResponse
的实例,表示请求和响应信息。connection
:当 TCP 连接建立时,该事件被触发,提供一个参数 socket,为 net.Socket 的实例。connection事件的粒度要大于 request, 因为客户端在 Keep-Alive 模式下可能会在同一个连接内发送多次请求。http.createServer([requestListener])
, 功能是创建一个 HTTP 服 务 器 并 将 requestListener 作为 request 事件的监听函数,这也是我们前面例子中使用的方法。 事实上它显式的实现方法是:2. http.ServerRequest http.ServerRequest 是 HTTP 请求的信息,是后端开发者最关注的内容。它一般由http.Server 的 request事件发送,作为第一个参数传递,通常简称 request 或 req。 http.ServerRequest 提供了以下3个事件用于控制请求体传输:
data
:当请求体数据到来时,该事件被触发。该事件提供一个参数 chunk,表示接 收到的数据。如果该事件没有被监听,那么请求体将会被抛弃。该事件可能会被调 用多次。end
: 当请求体数据传输完成时,该事件被触发,此后将不会再有数据到来。close
: 户当前请求结束时,该事件被触发。不同于end,如果用户强制终止了传输,也还是调用close。3.获取GET请求内容
url.parse
Demo:
4. 获取POST请求内容 POST 请求的内容全部都在请求体中。 http.ServerRequest并没有一个属性内容为请求体,原因是等待请求体传输可能是一件 耗时的工作,譬如上传文件。而很多时候我们可能并不需要理会请求体的内容,恶意的 POST 请求会大大消耗服务器的资源。所以Node.js 默认是不会解析请求体的,当你需要的时候, 需要手动来做。 一个Demo
上面代码并没有在请求响应函数中向客户端返回信息,而是定义了一个 post 变量,用 于在闭包中暂存请求体的信息。通过 req 的 data 事件监听函数,每当接受到请求体的数据, 就累加到 post 变量中。在 end 事件触发后,通过 querystring.parse 将 post 解析为 真正的 POST 请求格式,然后向客户端返回。
5. http.ServerResponse http.ServerResponse 是返回给客户端的信息,决定了用户最终能看到的结果。它 也是由 http.Server 的 request 事件发送的,作为第二个参数传递,一般简称为 response 或 res。http.ServerResponse 有三个重要的成员函数,用于返回响应头、响应内容以及结束请求。
response.writeHead(statusCode, [headers])
: 向请求的客户端发送响应头。headers 是一个类似关联数组的对象,表示响应头的每个属性。该函数在一个请求内最多只 能调用一次,如果不调用,则会自动生成一个响应头。response.write(data, [encoding])
: 向请求的客户端发送响应内容。data 是 一个 Buffer 或字符串,表示要发送的内容。如果 data 是字符串,那么需要指定 encoding 来说明它的编码方式,默认是 utf-8。在 response.end 调用之前, response.write 可以被多次调用。response.end([data], [encoding])
:结束响应,告知客户端所有发送已经完 成。当所有要返回的内容发送完毕的时候,该函数 必须 被调用一次。它接受两个可 选参数,意义和 response.write 相同。如果不调用该函数,客户端将永远处于 等待状态。HTTP客户端
http 模块提供了两个函数 http.request 和http.get,功能是作为客户端向 HTTP 服务器发起请求。
http.request(options, callback) 发起 HTTP 请求。接受两个参数,option 是 一个类似关联数组的对象,表示请求的参数,callback 是请求的回调函数。option 常用的参数如下所示。
callback 传递一个参数,为http.ClientResponse的实例。 http.request 返回一个http.ClientRequest的实例。
http.get(options, callback)
http 模块还提供了一个更加简便的方法用于处 理GET请求:http.get。它是 http.request 的简化版,唯一的区别在于http.get 自动将请求方法设为了 GET 请求,同时不需要手动调用 req.end()。 如:http.ClientRequest 像 http.ServerResponse 一样也提供了 write 和 end 函 数,用于向服务器发送请求体,通常用于 POST、PUT 等操作。所有写结束以后必须调用 end函数以通知服务器,否则请求无效。http.ClientRequest 还提供了以下函数。
request.abort()
:终止正在发送的请求。request.setTimeout(timeout, [callback])
:设置请求超时时间,timeout 为毫秒数。当请求超时以后,callback 将会被调用。Express
控制权转移
Express支持同一路径绑定多个路由响应函数,如:
但当你访问任何被这两条同样的规则匹配到的路径时,会发现请求总是被前一条路由规 则捕获,后面的规则会被忽略。原因是 Express 在处理路由规则时,会优先匹配先定义的路由规则,因此后面相同的规则被屏蔽。 Express 提供了路由控制权转移的方法,即回调函数的第三个参数next,通过调用 next(),会将路由控制权转移给后面的规则,例如:
当访问被匹配到的路径时,如
http://localhost:3000/user/carbo
,会发现终端中打印了all methods captured
,而且浏览器中显示了user: carbo
。这说明请求先被第一条路由规 则捕获, 完成console.log
后使用next()
转移控制权,又被第二条规则捕获,向浏览器返回了信息.Node.js进阶
模块加载
文件模块的加载有两种方式,一种是按路径加载,一种是查找 node_modules 文件夹。如果 require 参数以“ / ”开头, 那么就以绝对路径的方式查找模块名称,例如
require ('/home/byvoid/module')
将会按照优先级依次尝试加载/home/byvoid/module.js
、/home/byvoid/module.json
和/home/byvoid/module.node
。如果 require 参数以
" ./ "
或" ../"
开头, 那么则以相对路径的方式来查找模块,这种方式在应用中是最常见的通过查找node_modules目录加载
如果require参数不以
"/ "
、" ./ "
或" ../ "
开头, 而该模块又不是核心模块,那么就 要通过查找 node_modules 加载模块了。我们使用npm获取的包通常就是以这种方式加载的。控制流问题
循环的陷阱
另一种写法
Node.js应用部署
1. 日志功能
Express支持两种模式,开发模式和产品模式,前者的目的是利于调试,后者则是利于部署。使用产品模式运行服务器的方式很简单,只需设置
NODE_ENV
环境变量。通过NODE_ENV=production node app.js
命令运行服务器可以看到:Express 提供了一个访问日志中间件,只需指定stream 参数为一个输出流即可将访问日志写入文件。打开app.js,在最上方加入以下代码: