模块路径解析
require路径解析支持类似foo/bar
的写法,按照以下规则解析路径:
1、内置模块
如require('fs')
2、node_modules目录
node_modules
是特殊目录,如某个模块的绝对路径是 /home/user/hello.js , require('foo/bar')
将以下面路径作为解析:
/home/user/node_modules/foo/bar
/home/node_modules/foo/bar
/node_modules/foo/bar
3、NODE_PATH环境变量
与PATH环境变量类似,Node通过 NODE_PATH 环境变量来指定额外的模块搜索路径。 NODE_PATH=/home/user/lib:/home/lib
包(package)
其实是一个目录,里面包括多个模块js,其中需包含一个入口模块,当require这个目录时,其实就是require入口模块。入口模块包含了其他模块。 默认入口模块为index.js,如果想自定义入口模块,则在该目录下packge.json里面定义:
// package.json
{
"name": "cat",
"main": "./lib/custom.js"
}
命令行程序
Node开发的成品最终要么是一个包,要么是一个命令行程序;而且前者最终也是用于开发后者。所以最好是以命令行的方式来运行模块。 至于如何把js模块配置为系统命令行,请参考这里, 包含Linux和Windows的配置教程。
文件操作
大致介绍与文件操作相关的API:
Buffer(数据块)
Buffer处理二进制数据。不同于字符串修改(字符串任何修改得到的都是一个新字符串),Buffer像做指针操作的C语言数组。 除了读取文件得到Buffer的实例外,下面有更多用法:
// 直接构造
var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]);
// 获取相应位置字节
bin[0]; // => 0x68;
// 转为字符串
var str = bin.toString('utf-8'); // => "hello"
// 转为二进制编码
var bin = new Buffer('hello', 'utf-8'); // => <Buffer 68 65 6c 6c 6f>
.slice
方法不同于数组的.slice
方法,返回的是指向原Buffer的某个位置的指针,所以对.slice
返回的Buffer的修改,会作用于原Buffer
。
Stream(数据流), File System
Stream对数据边读取边处理。 下面为拷贝大文件的实例程序:
var rs = fs.createReadStream(src); // 创建一个只读数据流
var ws = fs.createWriteStream(dst) // 创建一个只写数据流
rs.on('data', function(chunk){
if(false === ws.write(chunk)){
rs.pause() // 先暂停,免得不停触发data应付不过来
}
})
rs.on('end', function(){
clearup()
})
rs.on('drain', function(){
rs.resume()
})
fs
提供的API基本有下面3类:
- 文件属性的读写:
fs.stat
,fs.chmod
,fs.chown
等等 - 文件内容的读写:
fs.readFile
,fs.readdir
,fs.writeFile
,fs.mkdir
等等 - 底层文件的操作:
fs.open
,fs.read
,fs.write
,fs.close
等等
Path
-
path.normalize 解析路径中的
.
和..
外,还能去除多余的斜杠,可用于比如标准化用户随意输入的路径。 **注意,**标准化后,斜杠在Windows下是\
, Linux是/
。如果要保证是/
,需要再.replace(/\\/g, '/')
-
path.join 将传入的路径拼接为标准路径。
path.join('foo/', 'bax/', '../bar') // => foo/bar
-
path.extname 获取扩展名,可用于比如对不同扩展名的文件进行不同的操作:
path.join('foo/bar.js') // => '.js'
遍历目录
简单同步遍历目录示例:
function travel(dir, callback){
fs.readdirSync(dir).forEach(function(file){
var pathname = path.join(dir, file)
if(fs.statSync(pathname).isDirectory()){
travel(pathname, callback)
}else{
callback(pathname)
}
})
}
异步遍历示例:
function travel(dir, callback, finish){
fs.readdir(dir, function(err, files){
var fileLength = files.length
(function next(i){
if(fileLength > i){
var pathname = path.join(dir, files[i])
fs.stat(pathname, function(err, stats){
if(stats.isDirectory()){
travel(pathname, callback, function(){
next(i+1)
})
}else{
callback(pathname, function(){
next(i+1)
})
}
})
}else{
finish && finish()
}
})(0)
})
}
文件编码
常用编码为UTF8和GBK,有些UTF8带BOM的。读取不同编码的文本,需转换为JS使用的UTF8编码才能正常处理。
BOM的移除
BOM会在文本文件头部以一个Unicode编码(如UTF16BE, UTF8)的字符来标记文件编码,但通常需要先移除BOM再作node的处理。
以下编码去除UTF8 BOM:
function readText(pathname){
var bin = fs.readFileSync(pathname)
if (bin[0] === 0xEF && bin[1] === 0xBB && bin[2] === 0xBF) {
bin = bin.slice(3);
}
return bin.toString('utf-8');
}
GBK to UTF8
node自身不支持GBK,需借助如iconv-lite
第三方包转换编码。
var iconv = require('iconv-lite')
function readGBKText(pathname){
var bin = fs.readFileSync(pathname)
return iconv.decode(bin, 'gbk')
}
单字节编码
不管源文本文件是什么编码,都用单字节编码(即保存英文字符的那种编码)进行保存和读取。虽然对于中文字符那些会乱码,但以相同的单字节编码进行读写即可。 Node提供binary
编码来实现这个方法:
function replace(pathname){
var str = fs.readFileSync(pathname, 'binary')
str = str.replace('foo', 'bar')
fs.writeFileSync(pathname, str, 'binary')
}
网络编程
http模块
此模块主要作用为: 1、作服务器,监听HTTP客户端请求并返回响应 2、作客户端,发起请求,获取服务器响应
可以用request
对象获取 请求头数据,同时以读取数据流的方式获取 请求体数据:
http.createServer(function(request, response){
var bodyarr = [] , body ;
console.log(request.method)
console.log(request.headers)
request.on('data', function(chunk){
bodyarr.push(chunk)
})
request.on('end', function(){
body = Buffer.concat(bodyarr)
console.log(body.toString())
})
}).listen(80)
相对应地,利用response
对象直接写入 响应头数据,以数据流的方式写入 响应体数据,只不过是边写边响应的方式:
http.createServer(function(request, response){
response.write(200, {'Content-Type': 'text/plain'})
request.on('data', function(chunk){
response.write(chunk)
})
request.on('end', function(){
response.end()
})
}).listen(80)
https模块
https模块与http模块极为类似,区别在于https模块需额外处理SSL证书。
var options = {
key: fs.readFileSync('./ssl/default.key'), // 私钥
cert: fs.readFileSync('./ssl/default.cer') // 公钥
};
var server = https.createServer(options, function(request, response) {
// ...
});
node支持同一HTTPS服务器使用多个域名提供服务:
server.addContext('foo.com', {
key: fs.readFileSync('./ssl/foo.com.key'),
cert: fs.readFileSync('./ssl/foo.com.cert')
})
server.addContext('bar.com', {
key: fs.readFileSync('./ssl/bar.com.key'),
cert: fs.readFileSync('./ssl/bar.com.cer')
})
发起客户端请求:
var options = {
hostname: 'www.example.com',
port: 443,
path: '/',
method: 'GET'
/*
,
rejectUnauthorized: false // 禁止对证书有效性的检查,以允许使用自制证书的HTTPS服务器
*/
}
var request = https.request(options, function(response){})
request.end()
URL
parse模块
url.parse
方法加上request.url
属性是处理HTTP请求时的固定搭配。
url.parse('http://user:pass@host.com:8080/p/a/t/h?query=string#hash');
/* =>
{ protocol: 'http:',
slashes: true,
auth: 'user:pass',
host: 'host.com:8080',
hostname: 'host.com',
port: '8080',
hash: '#hash',
search: '?query=string',
query: 'query=string',
pathname: '/p/a/t/h',
path: '/p/a/t/h?query=string',
href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash' }
*/
.parse
方法接受参数时,不一定要完整的url。传进来的url有哪部分,返回的对象就有哪些属性,没有的就为null。 .parse
方法还支持第二个和第三个布尔类型可选参数。 第二个参数等于true时,该方法返回的URL对象中,query
字段不再是一个字符串,而是一个经过querystring模块转换后的参数对象。 第三个参数等于true时,该方法可以正确解析不带协议头的URL,例如//www.example.com/foo/bar>
。
format模块
相反,.format
将URL对象转换为URL字符串:
url.format({
protocol: 'http:',
host: 'www.example.com',
pathname: '/p/a/t/h',
search: 'query=string'
});
/* =>
'http://www.example.com/p/a/t/h?query=string'
*/
querystring模块
querystring.parse('foo=bar&baz=qux&baz=quux&corge');
/* =>
{ foo: 'bar', baz: ['qux', 'quux'], corge: '' }
*/
querystring.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' });
/* =>
'foo=bar&baz=qux&baz=quux&corge='
*/
zlib模块
zlib进行数据压缩和解压。可用来压缩请求体数据/响应体数据。
Net模块
Net模块可创建Socket服务器和Socket客户端。Socket服务器与客户端可对HTTP协议做底层操作。
进程管理
示例:使用子进程调用终端命令实现目录拷贝:
var child_process = require('child_process')
var util = require('util')
function copy(src,dst,callback){
child_process.exec( util.format('cp -r %s/* %s', src, dst ), callback)
}
copy('a', 'b', function(err){})
process
注意 process 不是一个模块,而是一个全局变量,感知和控制node自身进程的方方面面。 一个程序正常退出,退出状态码为0。但如果是发生异常挂了,退出状态码就不为0了,可设置为指定数字:
try{}
catch(e){
process.exit(1)
}
process.stdin
标准输入流,只读process.stdout
标准输出流,只写process.stderr
标准错误流,只写
// console.log的实现
function log(){
process.stdout.write( util.format.apply(util, arguments) + '\n' )
}
如何降权例子:
在linux下监听1024以下端口需要root权限。完成监听后,继续让程序运行在root权限下存在安全隐患,故需降权。 通过sudo来获取root,运行程序的用户的UID和GID保存在SUDO_UID
和SUDO_GID
通过chmod +s
获取root权限,用户的UID和GID可通过process.getuid
和process.getgid
来获取。
降权必须先降GID再降UID,反之则没权限更改GID了
http.createServer(callback).listen(80, function(){
var env = process.env,
uid = parseInt(env['SUDO_UID'] || process.getuid() , 10),
gid = parseInt(env['SUDO_GID'] || process.getgid() , 10);
process.setgid(gid)
process.setuid(uid)
})
child_process
child_process是一个模块,最核心的API是.spawn
。 创建子进程示例:
var child = child_process.spawn('node', ['xxx.js'])
child.stdout.on('data', function(data){
console.log('stdout: ' + data)
})
child.stderr.on('data', function(data){
console('stderr: ' + data)
})
child.on('close', function(code){
console.log('child process exited with code ' + code)
})
.spawn(exec, args, options)
方法,该方法支持三个参数。第一个参数是执行文件路径,可以是执行文件的相对或绝对路径,也可以是根据PATH环境变量能找到的执行文件名。第二个参数中,数组中的每个成员都按顺序对应一个命令行参数。第三个参数可选,用于配置子进程的执行环境与行为。 子进程的.stdout
和.stderr
的访问子进程的输入输出,但options.stdio
字段的配置可使子进程的输入输出重定向至任何数据流,或父进程的输入输出流,或直接忽略。
进行间的通讯示例:
/* parent.js */
var child = child_process.spawn('node', ['child.js'])
child.kill('SIGTERM')
/* child.js */
process.on('SIGTERM', function(){
clearup()
process.exit(0)
})
不要被.kill
名字迷惑了,它实际是给进程发送信号的。 如果父子进程都是Nodejs进程,就可以通过IPC(进程间通讯)双向传递数据:
/* parent.js */
var child = child_process.spawn('node', ['child.js'], {
stdio: [0,1,2,'ipc']
})
child.on('message', function(msg){
console.log(msg)
})
child.send({hello: 'hello'})
/* child.js */
process.on('message', function(msg){
msg.hello = msg.hello.toUpperCase()
process.send(msg)
})
父进程在创建子进程时,在options.stdio
字段通过ipc
开启了IPC通道,父子进程可相互通讯。
创建守护进程示例:
守护进程在监控工作进程的运行状态,在工作不正常时重启工作进程。
function spawn(mainModule){
var worker = child_process.spawn('node', [mainModule])
worker.on('exit', function(code){
if(0 !== code){
spawn(mainModule)
}
})
}
spawn('main.js')
Cluster
cluster模块是对child_process模块的进一步封装,专用于单线程nodejs web服务器无法利用多核CPU的问题。使用这模块能简化多进程服务器程序的开发,让每个核运行一个工作进程,并统一通过主进程监听端口和分发请求。
node的异步设计模式
通过domain来捕获异常
在一个运行环境中,异常如果没有被捕获,将作为全局异常被抛出。而domain就是一个js的运行环境。 在主运行环境,可通过process.on('uncaughtException', function(){})
来捕获异常。
http.createServer(function(request, response){
var d = domain.create()
d.on('error', function(){
response.writeHead(500)
response.end()
})
d.run(function(){
async(request, function(){
response.writeHead(200)
response.end(data)
})
})
})
node官方建议处理完异常后立即重启程序,而不是让程序继续运行。虽然js本身的异常处理机制不会导致内存泄露,但nodejs大量API都是通过C/C++实现的,可能会导致C/C++部分代码表现异常,进而导致内存泄露等问题。
在mac下卸载node
-
把这段shell脚本保存为sh文件,如uninstallNode.sh:
lsbom -f -l -s -pf /var/db/receipts/org.nodejs.pkg.bom \ | while read i; do sudo rm /usr/local/${i} done sudo rm -rf /usr/local/lib/node \ /usr/local/lib/node_modules \ /var/db/receipts/org.nodejs.*
-
更改sh权限: chmod 777 ./uninstallNode.sh
-
执行sh:./uninstallNode.sh
**附加:**如果执行了上述脚本后找不到org.nodejs.pkg.bom,然后npm命令是没有了,但node命令还运行。那就把路径/usr/local/bin下的node命令删去,保证不使用原来的node命令即可
npm模块发布:npm publish
发布模块:
基本发布流程:http://segmentfault.com/a/1190000000491188
更新模块:
模块发布出去后,以后的模块更新步骤如下:
- 不能手动来更新package.json的版本号,需要调用这个命令:
npm version <update_type>
,update_type的取值为:patch,minor,major。分别从小到大更新模块版本号。 npm publish
发布出去即可- 把最新代码push到git上去
###知识点记录
-
使用了大淘宝的 npm 镜像安装Node:
npm install express --registry=https://registry.npm.taobao.org
-
require.main
if (require.main === module) { // 如果是直接执行 main.js,则进入此处 // 如果 main.js 被其他文件 require,则此处不会执行。 }
-
假设你有一个项目A,用到了 mocha 的 version 3,其他人有个项目B,用到了 mocha 的 version 10,那么如果你 npm i mocha -g 装的是 version 3 的话,你用 $ mocha 是不兼容B项目的。因为 mocha 版本改变之后,很可能语法也变了,对吧。 这时,跑测试用例的正确方法,应该是:
npm i mocha --save-dev
,装个 mocha 到项目目录中去./node_modules/.bin/mocha
,用刚才安装的这个特定版本的 mocha,来跑项目的测试代码。./node_modules/.bin
这个目录下放着我们所有依赖自带的那些可执行文件。每次输入这个很麻烦对吧?所以我们要引入 Makefile,让 Makefile 帮我们记住复杂的配置。
test: ./node_modules/.bin/mocha cov test-cov: ./node_modules/.bin/istanbul cover _mocha .PHONY: test cov test-cov
这时,我们只需要调用
make test
或者make cov
,就可以跑我们相应的测试了。 -
npm install -g without sudo, http://devin-clark.com/global-npm-install-without-sudo/
-
在jade模板里面以下两个模板写法,不同之处在于对undefined值的处理:
// person = {name: undefined} input(value="#{person.name}") // undefined input(value=person.name) // ''
-
利用
req.session
保存状态:req.session.user = user;
-
在获取req里的参数有以下几种方法:
var user = req.query.user; // or var user = req.body.user; // or var user = req.params.user; // or,不管是GET还是POST,都可获取 // 但对于这种方式,面对这样的路由: // /userlist/:user?user=xxx,并且request的body也有{user:xxxx} // 获取的排序是:params(:user) > body({user}) > query(?user=) var user = req.param('user');
-
启动node程序使用特定端口:
PORT-400 node app
,或者在程序代码中指定port -
npm list -g --depth 0
可以只显示一级的层级的已安装模块 -
process
是全局变量,process.argv
可获取命令行参数。 命令行参数的格式:argv[0]
固定等于node执行程序的绝对路径,argv[1]
固定等于主模块的绝对路径,所以第一个命令行参数从argv[2]这个位置开始。 通常这样来传递命令行参数main(process.argv.slice(2)); function main(argv){}
-
要安装特定版本的npm模块,在包名后面加
@<version>
:npm install argv@0.0.1
可用来解决
The package xxxx does not satisfy its siblings peerDependencies requirements!
的问题,先卸载当前的版本npm uninstall xxxx --save
,然后再安装特定版本:npm i xxxx@~1.4 --save
。 -
在package.json所在目录下使用
npm install . -g
可先在本地安装当前命令行程序,可用于发布前的本地测试。 -
使用
npm cache clear
可以清空NPM本地缓存,用于对付使用相同版本号发布新版本代码的人。 -
发起客户端HTTP请求前需要先创建一个客户端。http模块提供了一个全局客户端
http.globalAgent
,可以让我们使用.request或.get方法时不用手动创建客户端。但是全局客户端默认只允许5个并发Socket连接,当某一个时刻HTTP客户端请求创建过多,超过这个数字时,就会发生socket hang up错误。解决方法也很简单,通过http.globalAgent.maxSockets
属性把这个数字改大些即可。 另外,https模块遇到这个问题时也一样通过https.globalAgent.maxSockets
属性来处理。 -
自定义npm命令,以mocha-phantomjs测试举例: 1、在package.json定义:
"scripts":{ "test": "./node_modules/.bin/mocha-phantomjs vendor/index.html" }
2、将mocha-phantomjs作为依赖:
npm i mocha-phantomjs --save-dev
3、这时即可运行:npm test
-
npm init
命令帮助我们生成简单的package.json -
REST-Representational State Transfer
- 使用特定的HTTP方法:获取数据GET(直接url访问),创建数据POST(通过form提交),修改数据PUT,删除数据DELETE(通过ajax的type:'DELETE'来提交)
- 无状态:把状态的保存放在客户端
- 像文件目录式的URI路径
- 传输xml, json,或者both